/*
 * chan_oh323.c
 *
 * Generic OpenH323 Channel Driver for ASTERISK PBX.
 * 
 * Copyright (C) 2002-2005 Inaccess Networks.
 * Michael Manousos <manousos@inaccessnetworks.com>
 *
 * This file is part of "H.323 support for ASTERISK"
 *
 * "H.323 support for ASTERISK" is free software;
 * you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or (at your option) any later version. 
 *
 * "H.323 support for ASTERISK" is distributed in the hope that it will be 
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details. 
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 * In-Band DTMF detection is based on code sent by Klaus-Peter Junghanns
 * in ASTERISK mailing list.
 *
 * $Id: chan_oh323.c,v 1.2 2010/05/04 09:02:57 mmanousos Exp $
 *
 */

/******************************************************************************/
/* System and application's include files *************************************/
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <math.h>
#include <netinet/ip.h>
#include <sys/signal.h>

#include "asterisk.h"

#include "asterisk/lock.h"
#include "asterisk/channel.h"
#include "asterisk/config.h"
#include "asterisk/logger.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/options.h"
#include "asterisk/sched.h"
#include "asterisk/io.h"
#include "asterisk/rtp.h"
#include "asterisk/udptl.h"
#include "asterisk/acl.h"
#include "asterisk/manager.h"
#include "asterisk/callerid.h"
#include "asterisk/cli.h"
#include "asterisk/app.h"
#include "asterisk/musiconhold.h"
#include "asterisk/dsp.h"
#include "asterisk/features.h"
#include "asterisk/srv.h"
#include "asterisk/astdb.h"
#include "asterisk/causes.h"
#include "asterisk/utils.h"
#include "asterisk/file.h"
#include "asterisk/astobj.h"
#include "asterisk/devicestate.h"
#include "asterisk/linkedlists.h"
#include "asterisk/stringfields.h"
#include "asterisk/monitor.h"
#include "asterisk/localtime.h"
#include "asterisk/abstract_jb.h"
#include "asterisk/compiler.h"
#include "asterisk/threadstorage.h"
#include "asterisk/translate.h"

#include "wrapper.hxx"
#include "oh323.h"

/******************************************************************************/
/* String variables required by ASTERISK **************************************/
static const char type[] = "OH323";
static const char desc[] = "InAccess Networks OpenH323 Channel Driver";
static const char tdesc[] = "InAccess Networks OpenH323 Channel Driver";
static const char config[] = "oh323.conf";

/******************************************************************************/
/* G.723.1 frame handling *****************************************************/

#undef	DEBUG_CIRCBUFFER
static unsigned const G7231FrameSize[4] = { 24, 20, 4, 1};
#define	G7231SF_BUFSIZE	4096
struct G7231SF {
	unsigned char buffer[G7231SF_BUFSIZE];
	int head;			/* The position where free space begins */
	int tail;			/* The position of a whole G.723.1 frame */
	int free_space;		/* The available space of the buffer */
};

/* Function prototypes */
struct G7231SF * G7231SF_new(void);
void G7231SF_free(struct G7231SF *s);
int G7231SF_push(struct G7231SF *s, char *buf, int len);
int G7231SF_pop(struct G7231SF *s, char *data, int len, int *framenum);
int G7231SF_isempty(struct G7231SF *s);

/* Function implementation */
struct G7231SF * G7231SF_new(void)
{
	struct G7231SF *s;
	s = malloc(sizeof(struct G7231SF));
	if (s == NULL) {
		ast_log(LOG_WARNING, "Failed to create G.723.1 SF.\n");
		return(NULL);
	}
	memset(s, 0, sizeof(struct G7231SF));
	s->head = 0;
	s->tail = 0;
	s->free_space = G7231SF_BUFSIZE;
	return(s);
}
void G7231SF_free(struct G7231SF *s)
{
	if (s)
		free(s);
}
/**
 * buf - The buffer holding the data
 * len - the length of data in "buf"
 *
 * The return value is -1 on error, 0 on success.
 */
int G7231SF_push(struct G7231SF *s, char *buf, int len)
{
	int tmp1;
	if (len >= s->free_space) {
		ast_log(LOG_WARNING, "No more space in G.723.1 SF.\n");
		return(-1);
	}
	if (s->head + len > G7231SF_BUFSIZE) {	/* Wrap around */
		tmp1 = G7231SF_BUFSIZE - s->head;
		memcpy((char *)(s->buffer + s->head), buf, tmp1);
		memcpy((char *)(s->buffer), (char *)(buf + tmp1), len - tmp1);
		s->head = len - tmp1;
		s->free_space -= len;
#ifdef	DEBUG_CIRCBUFFER
		ast_log(LOG_DEBUG, "PUSH: Buffer wrap head=%d, tail=%d free=%d.\n", 
					s->head, s->tail, s->free_space);
#endif
	} else {	/* No wrap */
		memcpy((char *)(s->buffer + s->head), buf, len);
		s->head += len;
		s->free_space -= len;
	}
#ifdef	DEBUG_CIRCBUFFER
	ast_log(LOG_DEBUG, "PUSH: head=%d, tail=%d, free=%d.\n", 
					s->head, s->tail, s->free_space);
#endif
	return(0);
}
/**
 * data - The buffer where the data will be stored
 * len - The length of the "data" buffer
 * framenum - If this has a value <= 0, then
 *            the function will return all the whole frames available
 *            in the internal buffer. If this has a value > 0, it will
 *            return up to this number of frames. This argument is updated
 *            with the number of frames read.
 *
 * The return value is -1 on error or the number of bytes stored in the
 * "data" buffer.
 */
int G7231SF_pop(struct G7231SF *s, char *data, int len, int *framenum)
{
	int tmp0, tmp1, tmp2, cur_index, framelim;
	if (s->free_space == G7231SF_BUFSIZE) {
		ast_log(LOG_WARNING, "G.723.1 SF is empty.\n");
		*framenum = 0;
		return(-1);
	}
	framelim = *framenum;
	*framenum = 0;
	cur_index = s->tail;
	tmp1 = 0;
	tmp0 = 0;
	while (1) {
		cur_index += tmp1;
		if (cur_index >= G7231SF_BUFSIZE)
			cur_index = cur_index - G7231SF_BUFSIZE;
		if (tmp1 >= G7231SF_BUFSIZE - s->free_space) {
			if (tmp1 > G7231SF_BUFSIZE - s->free_space) {
				tmp1 -= tmp0;
				--(*framenum);
			}
			if (tmp1 == 0) {
				ast_log(LOG_WARNING, "G.723.1 SF contains no full frames.\n");
				*framenum = 0;
				return(-1);
			}
			if (tmp1 > len) {
				ast_log(LOG_WARNING, "Not enough space to store G.723.1 frame.\n");
				*framenum = 0;
				return(-1);
			}
			break;
		}
		if (*framenum == framelim)
			break;
		tmp0 = G7231FrameSize[((char *)(s->buffer))[cur_index] & 0x3];
#ifdef	DEBUG_CIRCBUFFER
		ast_log(LOG_DEBUG, "Frame length is %d\n", tmp0);
#endif
		tmp1 += tmp0;
		++(*framenum);
	}
	memset(data, 0, len); /* XXX */
	if (s->tail + tmp1 > G7231SF_BUFSIZE) { 	/* Wrap around */
		tmp2 = G7231SF_BUFSIZE - s->tail;
		memcpy(data, (char *)(s->buffer + s->tail), tmp2);
		memcpy((char *)(data + tmp2), s->buffer, tmp1 - tmp2);
		s->tail = tmp1 - tmp2;
		s->free_space += tmp1;
#ifdef	DEBUG_CIRCBUFFER
		ast_log(LOG_DEBUG, "POP: Buffer wrap head=%d, tail=%d free=%d.\n", 
					s->head, s->tail, s->free_space);
#endif
	} else {	/* No wrap */
		memcpy(data, (char *)(s->buffer + s->tail), tmp1);
		s->tail += tmp1;
		s->free_space += tmp1;
	}
#ifdef	DEBUG_CIRCBUFFER
	ast_log(LOG_DEBUG, "POP: head=%d, tail=%d free=%d.\n", 
					s->head, s->tail, s->free_space);
#endif
	return(tmp1);
}
int G7231SF_isempty(struct G7231SF *s)
{	
	if (s->free_space == G7231SF_BUFSIZE)
		return(1);
	return(0);
}
/******************************************************************************/

/* Configuration of the builtin call rate limiter (ingress direction) */

#define	MAX_INCALLS						20
#define	MAX_INCALLTIME					20000	/* In ms */
#define	MAX_INCALLTIME_CALLTHRESHOLD	30

typedef struct {
	struct timeval tv;
	struct timeval tdiff;
	int passed;
} time_struct;

/* Ingress call rate limiter function prototypes */
int in_call_rate_limiter_init(int, int);
void in_call_mark(int, int);
int in_call_rate_update(void);
int in_call_number_blocked(void);
int in_call_number_passed(void);
int in_call_time_get(void);
int in_call_blockratio_get(void);
int in_call_passratio_get(void);
int in_call_rate_get(void);

/* Ingress call rate limiter  parameters */
static int incall_times = 0;
static int incall_pos = 0;
static int incall_cur = 0;

static int incall_time_period = 0;
static int incall_max = 0;
static int incall_rate_limiter_status = 0;
static float in_call_max_rate = 999999.0;
static time_struct *intimes = NULL;

/**
 * Initialize the ingress call rate limiter.
 *
 * call_number : Max number of calls
 * call_time   : Time period in milliseconds
 * 
 *                         call_number
 * Maxrate (CPS) = --------------------------- * 1000
 *                   call_time (milliseconds)
 */
int in_call_rate_limiter_init(int call_number, int call_time)
{
	if (!call_number || !call_time) {
		/* Disable rate limiter */
		incall_rate_limiter_status = 0;
		in_call_max_rate = 999999.0;
		return(0);
	}
	incall_rate_limiter_status = 1;
	incall_max = call_number;
	incall_time_period = call_time;
	intimes = (time_struct *)malloc(sizeof(time_struct) * incall_max);
	if (intimes == NULL)
		return(-1);
	memset((char *)intimes, 0, sizeof(time_struct) * incall_max);
	if (incall_time_period > 0)
		in_call_max_rate = (float)incall_max * 1000 / (float)incall_time_period;
	else
		in_call_max_rate = 999999.0;
	return(0);
}

void in_call_mark(int index, int val)
{
	if ((!incall_rate_limiter_status) || (index < 0) || (index > incall_max-1))
		return;
	intimes[index].passed = val;
}

int in_call_rate_update(void)
{
	struct timeval tv;
	int prev;

	if (!incall_rate_limiter_status)
		return(0);

	if (gettimeofday(&tv, NULL) < 0)
		return(-1);
	if (incall_times < incall_max) {
		intimes[incall_times].tv = tv;
		if (incall_times == 0) {
			intimes[incall_times].tdiff.tv_sec = 0;
			intimes[incall_times].tdiff.tv_usec = 0;
		} else {
			if (tv.tv_usec < intimes[incall_times-1].tv.tv_usec) {
				tv.tv_usec += 1000000;
				tv.tv_sec -= 1;
			}
			intimes[incall_times].tdiff.tv_sec = tv.tv_sec - intimes[incall_times-1].tv.tv_sec;
			intimes[incall_times].tdiff.tv_usec = tv.tv_usec - intimes[incall_times-1].tv.tv_usec;
		}
		incall_cur = incall_times;
		incall_times++;
	} else {
		intimes[incall_pos].tv = tv;
		if (incall_pos == 0)
			prev = incall_max - 1;
		else
			prev = incall_pos - 1;
		if (tv.tv_usec < intimes[prev].tv.tv_usec) {
			tv.tv_usec += 1000000;
			tv.tv_sec -= 1;
		}
		intimes[incall_pos].tdiff.tv_sec = tv.tv_sec - intimes[prev].tv.tv_sec;
		intimes[incall_pos].tdiff.tv_usec = tv.tv_usec - intimes[prev].tv.tv_usec;
		incall_cur = incall_pos;
		incall_pos++;
		if (incall_pos == incall_max)
			incall_pos = 0;
	}
	/* Be pesimistic for the call */
	intimes[incall_cur].passed = 0;

	/*
	ast_log(LOG_DEBUG, "POS: %d, DIFF: %ld sec - %ld usec.\n",
					incall_cur,
					intimes[incall_cur].tdiff.tv_sec,
					intimes[incall_cur].tdiff.tv_usec);
	*/
	return(incall_cur);
}

int in_call_number_blocked(void)
{
	int i, blocked;

	if (!incall_rate_limiter_status)
		return(0);

	if (incall_times != incall_max)
		return(0);

	blocked = 0;
	for (i = 0; i < incall_max; i++)
		blocked += (intimes[i].passed ? 0 : 1);

	return(blocked);
}

int in_call_number_passed(void)
{
	int i, passed;

	if (!incall_rate_limiter_status)
		return(0);

	if (incall_times != incall_max)
		return(0);

	passed = 0;
	for (i = 0; i < incall_max; i++)
		passed += intimes[i].passed;

	return(passed);
}

int in_call_time_get(void)
{
	int i;
	struct timeval tv;
	long int total;

	if (!incall_rate_limiter_status)
		return(0);

	if (incall_times != incall_max)
		return(0);

	tv.tv_sec = 0;
	tv.tv_usec = 0;
	for (i = 0; i < incall_max; i++) {
		tv.tv_sec += intimes[i].tdiff.tv_sec;
		tv.tv_usec += intimes[i].tdiff.tv_usec;
	}
	total = tv.tv_sec * 1000 + tv.tv_usec / 1000; /* ms */
	/*ast_log(LOG_DEBUG, "%d in calls in %ld ms.\n", incall_max, total);*/

	return((int)total);
}

/* Return the % of ingress calls blocked */
int in_call_blockratio_get(void)
{
	int total_blocked, total_time;
	long int diff;
	struct timeval tv;

	if (!incall_rate_limiter_status)
		return(0);

	if (incall_times != incall_max)
		return(0);

	total_blocked = (in_call_number_blocked() * 100) / incall_max;
	total_time = in_call_time_get();

	if (total_time > 0) {
		if (gettimeofday(&tv, NULL) < 0)
			return(-1);
		diff = (tv.tv_sec - intimes[incall_cur].tv.tv_sec) * 1000 +
				(tv.tv_usec - intimes[incall_cur].tv.tv_usec) / 1000;
		return((total_blocked * total_time) / (total_time + diff));
	} else
		return(0);
}

/* Return the % of ingress calls passed */
int in_call_passratio_get(void)
{
	int total_passed, total_time;
	long int diff;
	struct timeval tv;

	if (!incall_rate_limiter_status)
		return(0);

	if (incall_times != incall_max)
		return(0);

	total_passed = (in_call_number_passed() * 100) / incall_max;
	total_time = in_call_time_get();

	if (total_time > 0) {
		if (gettimeofday(&tv, NULL) < 0)
			return(-1);
		diff = (tv.tv_sec - intimes[incall_cur].tv.tv_sec) * 1000 +
				(tv.tv_usec - intimes[incall_cur].tv.tv_usec) / 1000;
		return((total_passed * total_time) / (total_time + diff));
	} else
		return(0);
}

/* Return '100 x rate' in CPS */
int in_call_rate_get(void)
{
	int total_time;
	long int diff;
	struct timeval tv;

	if (!incall_rate_limiter_status)
		return(0);

	total_time = in_call_time_get();
	if (total_time > 0) {
		if (gettimeofday(&tv, NULL) < 0)
			return(-1);
		diff = (tv.tv_sec - intimes[incall_cur].tv.tv_sec) * 1000 +
				(tv.tv_usec - intimes[incall_cur].tv.tv_usec) / 1000;
		return((incall_max * 100000) / (total_time + diff));
	} else
		return(0);
}

/******************************************************************************/


/******************************************************************************/
/* Channel driver's internal stuff ********************************************/

MARKER_INIT;

/* Enable/Disable verbose debug info */
static int	oh323_verbose_debug = 0;

/** The capabilities (codecs) supported by H.323 channel driver */
static int oh323_capability = OH323_CAPABILITY_FULLBANDWIDTH;
static int oh323_full_capability = OH323_CAPABILITY_FULLBANDWIDTH;

/** Mutex for the usage counter */
static ast_mutex_t usecnt_lock;
static int usecnt = 0;

/** Mutex for the OpenH323 calls table */
static ast_mutex_t oh323_tab_lock;

/** Mutex for the unique id generator */
static ast_mutex_t oh323_uid_lock;
static unsigned int uid_base = 0;

/** Mutex for the driver's statistics structure */
static ast_mutex_t oh323_stats_lock;
struct {
	int incall;
	int outcall;
	int setup_sent;
	int setup_recv;
	int block_incall;
	int block_outcall;
	int proto_err;
	int call_err;
	int answer_err;
	struct timeval boot_time;
} oh323_stats;

/* Protect the monitoring thread, so only one process can kill or start it, 
   and not when it's doing something critical. */
static ast_mutex_t mon_lock;

/* This is the thread for the monitor which checks for input on the channels
   which are not currently in use.  */
static pthread_t monitor_thread = 0;
static int monitor_shutdown = 0;
static int monitor_running = 0;

/** Mutex for reloading the configuration of the driver */
static ast_mutex_t oh323_reload_lock;
static int oh323_reloading;

/* Scheduled actions (channel cleanups) on OH323 channels
 * and gatekeeper registration retries. */
static struct sched_context *oh323_sched;
static int oh323_sched_gkid;

/*
 * Channel variables used by OpenH323 channel driver
 */
static char *oh323_var_tab[] = {
	"OH323_TEST",
	"OH323_CALLID",		/* Call ID (W) */
	"OH323_CONFID",		/* Conference ID (W) */
	"OH323_CTOKEN",		/* Call Token (W) */
	"OH323_SRCALIAS",	/* Source alias(es) (W) */
	"OH323_DSTALIAS",	/* Destination alias(es) (W) */
	"OH323_SRCE164",	/* Source E.164 number (W) */
	"OH323_DSTE164",	/* Destination E.164 number (W) */
	"OH323_REMOTEAPP",	/* Remote application name (W) */
	"OH323_CHANCODEC",	/* The codec used by the channel (W) */
	"OH323_OUTCODEC",	/* The codec to be used for an outgoing 
						   H.323 call (R) */
	"OH323_RADDR",		/* The IP address of the remote end (W) */
	"OH323_LADDR",		/* The IP address of the local end (W) */
	"OH323_RRTP",		/* The remote RTP end of the call (W) */
	"OH323_LRTP",		/* The local RTP end of the call (W) */
	"OH323_CALL_ENDREASON",	/* Call end reason (W) */
	"OH323_CALL_DURATION",	/* Call duration (W) */
	"OH323_STATSFILE",	/* The file where the session stats will be
						   saved (R) */
	NULL
};

static num_str_t state_str[] = {
	{ OH323_STATE_NULL, "NULL" },
	{ OH323_STATE_INIT, "INIT" },
	{ OH323_STATE_RING, "RING" },
	{ OH323_STATE_RINGING, "RINGING" },
	{ OH323_STATE_ESTABLISHED, "ESTABLISHED" },
	{ OH323_STATE_CLEARED, "CLEARED" },
	{ -1, "UNKNOWN" }
};
static num_str_t dir_str[] = {
	{ NONE, "NONE" },
	{ PLAYER, "RX" },
	{ RECORDER, "TX" },
	{ BOTH, "BOTH" },
	{ -1, "UNKNOWN" }
};
static num_str_t dtmf_str[] = {
	{ UIMODE_Q931, "q931" },
	{ UIMODE_STRING, "string" },
	{ UIMODE_TONE, "tone" },
	{ UIMODE_RFC2833, "rfc2833" },
	{ UIMODE_INBAND, "inband" },
	{ -1, "unknown" }
};
static num_str_t codec_str[] = {
	{ G711U, "G711U" },
	{ G711A, "G711A" },
	{ G7231, "G7231" },
	{ G72316K3, "G72316K3" },
	{ G72315K3, "G72315K3" },
	{ G7231A6K3, "G7231A6K3" },
	{ G7231A5K3, "G7231A5K3" },
	{ G726, "G726" },
	{ G72616K, "G72616K" },
	{ G72624K, "G72624K" },
	{ G72632K, "G72632K" },
	{ G72640K, "G72640K" },
	{ G728, "G728" },
	{ G729, "G729" },
	{ G729A, "G729A" },
	{ G729B, "G729B" },
	{ G729AB, "G729AB" },
	{ GSM0610, "GSM0610" },
	{ MSGSM, "MSGSM" },
	{ LPC10, "LPC10" },
	{ SPEEXN8K, "SPEEXN8K" },
	{ -1, "UNKNOWN" }
};

/*
 * Structures for storing configuration 
 * options of H.323 channel driver.
 */
struct oh323_reginfo {
	char context[AST_MAX_EXTENSION];
	char **alias;
	int  alias_num;
	char **prefix;
	int  prefix_num;
	struct oh323_reginfo *next;
};

struct oh323_codecinfo {
	int codec;
	int format;
	int frames;
	struct oh323_codecinfo *next;
};

struct oh323_options {
	char listenAddress[128];
	int  listenPort;
	int  connectPort;
	int  tcpStart;		/* TCP (H.245) ports */
	int  tcpEnd;
	int  udpStart;		/* UDP (RAS) ports */
	int  udpEnd;
	int  rtpStart;		/* UDP (RTP) ports */
	int  rtpEnd;
	int  fastStart;
	int  h245Tunnelling;
	int  h245inSetup;
	int  jitterMin;
	int  jitterMax;
#ifdef HAS_OH323MODS
	int  jitterDec;
	int  jitterInc;
#endif
	int  ipTos;
	int  outboundMax;
	int  inboundMax;
	int  simultaneousMax;
	int  totalNum;
	int  bwlimit;
	int  crlCallNumber;			/* Call rate limiter params */
	int  crlCallTime;
	int  crlThreshold;
	int  wrapLibTraceLevel;		/* Wrapper library trace level */
	int  libTraceLevel;			/* OpenH323/Pwlib trace level */
	char libTraceFile[256];
	int  amaFlags;
	char accountCode[20];
	char language[MAX_LANGUAGE];
	char musicclass[MAX_LANGUAGE];
	gkmode_t gatekeeperMode;
	char gatekeeperName[128];
	char gatekeeperZone[128];
	char gatekeeperPass[128];
	int gatekeeperTTL;			/* TTL for our registration with the GK */
	uimode_t userInputMode;
	char context[AST_MAX_EXTENSION];
	char **alias_tab;
	int alias_num;
	char **gateway_prefix_tab;
	int prefix_num;
	struct oh323_reginfo *regInfo;
	struct oh323_codecinfo *codecInfo;
	/* The data of the "extensions" section are held in a separate struct */
};
static struct oh323_options config_options;

struct rtp_session {
	/* Null terminated strings with the following format:
	 *   address:RTP_port
	 * The RTCP port is 'RTP_port + 1'
	 */
	char local_addr[128];
	char remote_addr[128];
};

/******************************************************************************/

#define	DEFAULT_REQTIME		20	/* In milliseconds */

/**
 * Structures for executing requests (call initiation, call clearing, ...)
 * that do not require immediate return values.
 */
struct call_data {
	char addr[512];
	call_details_t cd;
	user_details_t ud;
};
struct answer_data {
	char token[1024];
};
struct digit_data {
	char token[1024];
	char digit;
};
struct text_data {
	char token[1024];
	char text[512];
};
struct indication_data {
	char token[1024];
	int  type;
};
struct hangup_data {
	char token[1024];
	int  q931_cause;
};
struct request_oh323 {
	/** Type of request */
	int type;
	/** Data, only one is valid (depends on 'type') */
	struct call_data call;
	struct answer_data answer;
	struct digit_data digit;
	struct text_data text;
	struct indication_data indication;
	struct hangup_data hangup;
	OBJLIST_DATA(struct request_oh323);
};
/**
 * The global request list.
 * The channel driver places requests in this list. The monitor thread
 * executes the placed requests.
 */
OBJLIST_DEFINE_STATIC(struct request_oh323, requestl);


/******************************************************************************/

/**
 * Structure for transfering data occurred inside 
 * an exception of OpenH323 Library.
 */
struct exception_oh323 {
	/** Type of exception data. */
	int type;
	/** Buffer with exception data */
	char data[MAX_EXCEPTION_DATA];
	OBJLIST_DATA(struct exception_oh323);
};

/******************************************************************************/

/**
 * H.323 endpoints we know about. The struct contains H.323 and Asterisk
 * related information.
 */
struct oh323_ep {
	/** The name of the entry */
	char name[128];
	/** H.323 details for this user/endpoint */
	user_details_t ud;
	/** The context where the calls of this endpoint are routed to */
	char context[128];
	/** The language for the calls of this endpoint */
	char language[128];
	/** The MOH class for the calls of this endpoint */
	char musicclass[128];
	int  amaflags;
	char accountcode[128];
	/** Next entry (easy linking) */
	struct oh323_ep *next;
};
static struct oh323_ep *oh323_eplist;
static ast_mutex_t oh323_eplock;

/******************************************************************************/

/**
 * Private structure of a OpenH323 channel.
 */
struct chan_oh323_pvt {

	/** Channel's file descriptors. The two arrays hold 
	 * 2 socketpairs. The first member of each array (index 0)
	 * is used inside the channel driver, whereas the 
	 * second member (index 1) is used inside the OpenH323.
	 * player_fd[0] is used to read data from the network
	 * (OpenH323 library) and record_fd[0] is used to write
	 * data to the network. player_fd[0] is select()'ed 
	 * inside ASTERISK. */
	int	player_fd[2];
	int	new_player_fd;
	int recorder_fd[2];
	int	new_recorder_fd;
	int close_player_fd, close_recorder_fd;

	char called_addr[AST_OH323_MAX_CALLED_ADDR];
	
	/** Event pipe to Asterisk. event_pipe[0] is select()'ed
	 * inside ASTERISK. */
	int event_pipe[2];

	/** Options to be used during call setup */
	call_options_t call_opt;

	/** Call details */
	call_details_t cd;

	/** Direction established */
	lchan_dir_t direction;

	/** State indication (OH323_STATE_*) */
	int i_state;

	/** Used for inband DTMF detection */
	struct ast_dsp *vad;

	/** The capability (codec) used by this channel */
	int capability;
	int rxcapability;
	int txcapability;
	
	/** Buffer sizes for RX/TX */
	int rx_buf_size, tx_buf_size;
	int rxcount, txcount;

	/** Endpoint's RTP info (address, port) */
	char ep_rtp_addr[4];
	int  ep_rtp_port;

	/** Owner if we have one */
	struct ast_channel *owner;

	/** Asterisk frame (used for reading frames) */
	struct ast_frame fr;

	/** Last known RX/TX formats */
	int last_tx_format, last_rx_format;

	/** Smoother struct for queuing frames for transmission */
	struct ast_smoother *tx_smooth;

	struct G7231SF *rx_g7231_smooth, *tx_g7231_smooth;

	/** Static buffer for reading frames */
	char buf[AST_FRIENDLY_OFFSET + OH323_MAX_BUF];
	char txtbuf[AST_FRIENDLY_OFFSET + OH323_MAX_BUF];

	/** Flag indicating whether the call was initiated from
	 * the remote endpoint or from ASTERISK. */
	int from_remote;

	/** Flag indicating whether the call was established or not */
	int established;

	/** Flag indicating whether PROGRESS has been sent  */
	int progress;

	/** Flag indicating that the call is being cleared */
	int clearing_call;

	/**
	 * Exception list.
	 * OpenH323 inserts exceptions in the channel's pvt exception list, through
	 * the callback functions, and the channel driver exctracts and processes them
	 * through the 'oh323_exception' function.
	 */
	OBJLIST_DEFINE(struct exception_oh323, exceptl);

	/** Endpoint settings for this channel, if they are defined */
	struct oh323_ep ep_settings;

	/** RTP session info */
	struct rtp_session rtp;

	/** RTP session statistics */
	rtp_stats_t rtp_stats[RTPSTATS_MAX_NUM];
	rtp_stats_t *head_stats, *tail_stats;
	ast_mutex_t stats_lock;

	/** Scheduler ID and current index */
	int sched_id, index;

	/** Already gone */
	int	alreadygone;

	/** The call must be destroyed */
	int needs_destroy;
	int normread;
};

/**
 * Array of OpenH323 calls (either established or inactive).
 */
static struct chan_oh323_pvt **oh323_tab;


/* Some function prototypes of the channel driver */
static void parse_dial_string(char *ds, char **exten, char **peer, char **port);
static char *build_address(char *addr, int addr_len, char *ext, char *peer, char *port,
				struct oh323_ep *ep);
static int find_call(const char *, unsigned int);
static void oh323_close_call_fds(int index);
static void	oh323_destroy(int);
static int	copy_call_details(call_details_t *, call_details_t *);
static void	clear_call_details(call_details_t *);
static int	oh323_codec2format(int);
static char *oh323_codecset2str(int, struct oh323_codecinfo *, char **);
static void oh323_format2codecset(int, int *, int);
static int context_from_alias(char *, char **);
static int context_from_prefix(char *, char **);
static int oh323_release(const void *);
static int oh323_exec_request(const void *data);
static struct oh323_ep *find_oh323_ep(char *epname, char *host, char *user);
void oh323_atexit(void);
unsigned int generate_uid(void);
static char *get_str(num_str_t tab[], int num);
static int get_num(num_str_t tab[], char *str);

/******************************************************************************/

static struct ast_channel *oh323_request(const char *type, int format, void *data, int *cause);
static int oh323_text(struct ast_channel *c, const char *text);
static int oh323_call(struct ast_channel *c, char *dest, int timeout);
static int oh323_hangup(struct ast_channel *c);
static int oh323_answer(struct ast_channel *c);
static struct ast_frame *oh323_read(struct ast_channel *c);
static int oh323_write(struct ast_channel *c, struct ast_frame *f);
static int oh323_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen);
static int oh323_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
static int oh323_digit(struct ast_channel *c, char digit, unsigned int duration);

static struct ast_channel_tech oh323_tech = {
	.type = type,
	.description = tdesc,
	.capabilities = OH323_CAPABILITY_FULLBANDWIDTH,
	.properties = 0,
	.requester = oh323_request,
	.devicestate = NULL,
	.send_digit_end = oh323_digit,
	.call = oh323_call,
	.hangup = oh323_hangup,
	.answer = oh323_answer,
	.read = oh323_read,
	.write = oh323_write,
	.send_text = oh323_text,
	.send_image = NULL,
	.send_html = NULL,
	.exception = NULL,
	.bridge = NULL,
	.indicate = oh323_indicate,
	.fixup = oh323_fixup,
	.setoption = NULL,
	.queryoption = NULL,
	.transfer = NULL,
	.write_video = NULL,
	.bridged_channel = NULL,
};

/******************************************************************************/

/*
 * CLI extensions 
 */
static int oh323_show_conf(int fd, int argc, char *argv[])
{
	char	*tmp, *gk_res;
	char	*gk_ptr, tmp_gk[200];

	if (argc != 3)
		return RESULT_SHOWUSAGE;
	ast_mutex_lock(&oh323_stats_lock);
	switch (h323_get_gk(tmp_gk, sizeof(tmp_gk))) {
	case 0:
		gk_res = "(Registered)";
		gk_ptr = (char *)tmp_gk;
		break;
	case OH323GK_FAILED:
		gk_res = "Failed";
		gk_ptr = "";
		break;
	case OH323GK_NOTREGISTERED:
		gk_res = "(Not registered)";
		gk_ptr = (char *)tmp_gk;
		break;
	case OH323GK_NOTUSED:
		gk_res = "No gatekeeper";
		gk_ptr = "";
		break;
	default:
		gk_res = "";
		gk_ptr = "";
		break;
	}
	ast_cli(fd, "\n");
	ast_cli(fd, " Configuration of OpenH323 channel driver \n");
	ast_cli(fd, "------------------------------------------\n");
	ast_cli(fd, "Version: %d.%d.%d\n", 
				OH323_VERSION_MAJOR, OH323_VERSION_MINOR, OH323_VERSION_BUILD);
	ast_cli(fd, "Listening on address: %s:%d\n", 
				config_options.listenAddress, config_options.listenPort);
	ast_cli(fd, "Gatekeeper used: %s %s\n", gk_ptr, gk_res);
	ast_cli(fd, "FastStart/H245Tunnelling/H245inSetup: %s/%s/%s\n", 
				config_options.fastStart!=0?"ON":"OFF",
				config_options.h245Tunnelling!=0?"ON":"OFF",
				config_options.h245inSetup!=0?"ON":"OFF");
	ast_cli(fd, "Supported formats in pref. order: %s\n", 
				oh323_codecset2str(1, config_options.codecInfo, &tmp));
	ast_cli(fd, "Jitter buffer limits (min/max): %d-%d ms\n", 
				config_options.jitterMin,
				config_options.jitterMax);
#ifdef HAS_OH323MODS
	ast_cli(fd, "Jitter buffer modification amounts (dec/inc): %d/%d frames\n", 
				config_options.jitterDec,
				config_options.jitterInc);
#endif
	/*ast_cli(fd, "Bandwidth limit: %.2f Kbps\n", config_options.bwlimit*100.0/1024.0);*/
	ast_cli(fd, "TCP port range: %d - %d\n", config_options.tcpStart, config_options.tcpEnd);
	ast_cli(fd, "UDP (RAS) port range: %d - %d\n", config_options.udpStart, config_options.udpEnd);
	ast_cli(fd, "UDP (RTP) port range: %d - %d\n", config_options.rtpStart, config_options.rtpEnd);
	ast_cli(fd, "IP Type-of-Service value: %d\n", config_options.ipTos);
	ast_cli(fd, "User input mode: %s\n", get_str(dtmf_str, config_options.userInputMode));
	ast_cli(fd, "Max number of inbound H.323 calls: %d\n", config_options.inboundMax);
	ast_cli(fd, "Max number of outbound H.323 calls: %d\n", config_options.outboundMax);
	ast_cli(fd, "Max number of simultaneous H.323 calls: %d\n", config_options.simultaneousMax);
	ast_cli(fd, "Max call rate (ingress direction): %.2f/%d\n", 
							in_call_max_rate, config_options.crlThreshold);
	ast_cli(fd, "Default language: %s\n", config_options.language);
	ast_cli(fd, "Default music class: %s\n", config_options.musicclass);
	ast_cli(fd, "Default context: %s\n", config_options.context);
	ast_cli(fd, "\n");
	if (tmp != NULL)
		free(tmp);
	ast_mutex_unlock(&oh323_stats_lock);
	return RESULT_SUCCESS;
}
static int oh323_show_stats(int fd, int argc, char *argv[])
{
	if (argc != 3)
		return RESULT_SHOWUSAGE;
	ast_mutex_lock(&oh323_stats_lock);
	ast_cli(fd, "\n");
	ast_cli(fd, "           Statistics of OpenH323 channel driver               \n");
	ast_cli(fd, "---------------------------------------------------------------\n");
	ast_cli(fd, "                            Up since: %s", ctime((time_t *) &(oh323_stats.boot_time.tv_sec)));
	ast_cli(fd, "     Established inbound H.323 calls: %6d\n", oh323_stats.incall);
	ast_cli(fd, "    Established outbound H.323 calls: %6d\n", oh323_stats.outcall);
	ast_cli(fd, "         Dropped inbound H.323 calls: %6d\n", oh323_stats.block_incall);
	ast_cli(fd, "        Blocked outbound H.323 calls: %6d\n", oh323_stats.block_outcall);
	ast_cli(fd, "  Total inbound H.323 calls detected: %6d\n", oh323_stats.setup_recv);
	ast_cli(fd, "Total outbound H.323 calls attempted: %6d\n", oh323_stats.setup_sent);
	ast_cli(fd, "                     Protocol errors: %6d\n", oh323_stats.proto_err);
	ast_cli(fd, "                  Call answer errors: %6d\n", oh323_stats.answer_err);
	ast_cli(fd, "              Call initiation errors: %6d\n", oh323_stats.call_err);
	ast_cli(fd, "\n");
	ast_mutex_unlock(&oh323_stats_lock);
	return RESULT_SUCCESS;
}
static int oh323_show_channels(int fd, int argc, char *argv[])
{
#define FORMAT_STRING  "%5s %-30s %-10s %-7s %-7s %-7s %4s/%-4s %-12s %-21s %-21s\n"
#define FORMAT_STRING2 "%5d %-30.30s 0x%08x %-7.7s %-7s %-7s %4d/%-4d %-12s %-21s %-21s\n"

	int i, count, established, res;
	char remote[100], local[100];

	if (argc != 3)
		return RESULT_SHOWUSAGE;
	count = 0;
	established = 0;
	ast_mutex_lock(&oh323_tab_lock);
	ast_cli(fd, "\n");
	ast_cli(fd, " Information about active OpenH323 channel(s) \n");
	ast_cli(fd, "----------------------------------------------\n");
	for (i=0; i<config_options.totalNum; i++)
		if (oh323_tab[i] != NULL) {
			if (count == 0)
				ast_cli(fd, FORMAT_STRING, 
						"Num.", "Token", "AppID", "State", "Dir", "Init", "RX", "TX", 
						"Format", "Remote RTP Addr.", "Local RTP Addr.");
			memset(remote, 0, sizeof(remote));
			memset(local, 0, sizeof(local));
			snprintf(remote, sizeof(remote)-1, "%s", 
							oh323_tab[i]->rtp.remote_addr);
			snprintf(local, sizeof(local)-1, "%s", 
							oh323_tab[i]->rtp.local_addr);
			ast_cli(fd, FORMAT_STRING2, count, 
				(oh323_tab[i]->cd.call_token != NULL ? oh323_tab[i]->cd.call_token:"NULL"),
				(oh323_tab[i]->ep_settings.ud.app_id),
				get_str(state_str, oh323_tab[i]->i_state),
				get_str(dir_str, oh323_tab[i]->direction),
				(oh323_tab[i]->from_remote ? "Remote":"Local"),
				oh323_tab[i]->rx_buf_size, oh323_tab[i]->tx_buf_size,
				ast_getformatname(oh323_tab[i]->capability),
				remote, local);
			++count;
			if (oh323_tab[i]->i_state == OH323_STATE_ESTABLISHED)
				++established;
		}
	if (count == 0)
		ast_cli(fd, "  [No active H.323 connections]\n");
	else {
		ast_cli(fd, "\n");
		ast_cli(fd, "  [%d established H.323 connection(s)]\n", established);
		if (incall_rate_limiter_status) {
			res = in_call_rate_get();
			ast_cli(fd, "  [In-call rate: %d.%02d CPS]\n", res / 100, res % 100);
			res = in_call_blockratio_get();
			ast_cli(fd, "  [In-call block ratio: %d %%]\n", res);
		}
	}
	ast_cli(fd, "\n");

	ast_mutex_unlock(&oh323_tab_lock);
	return RESULT_SUCCESS;
}
static int oh323_show_established(int fd, int argc, char *argv[])
{
	int i, established;

	if (argc != 3)
		return RESULT_SHOWUSAGE;
	established = 0;
	ast_mutex_lock(&oh323_tab_lock);
	for (i=0; i<config_options.totalNum; i++)
		if (oh323_tab[i] != NULL) {
			if (oh323_tab[i]->i_state == OH323_STATE_ESTABLISHED)
				++established;
		}
	ast_mutex_unlock(&oh323_tab_lock);
	ast_cli(fd, "\n");
	ast_cli(fd, "%d established H.323 connection(s)\n\n", established);
	return RESULT_SUCCESS;
}
static int oh323_show_ep(int fd, int argc, char *argv[])
{
#define	FORMAT_STRING2a	"%-14s %-24s %-12s %-12s %-10s %3s/%3s/%3s\n"

	struct oh323_ep *exttmp;
	char tmp[100];

	if (argc != 3)
		return RESULT_SHOWUSAGE;
	ast_mutex_lock(&oh323_eplock);
	ast_cli(fd, "\n");
	ast_cli(fd, FORMAT_STRING2a,
				"Entry", "Host", "Username", "Context", "Pref.Codec", "FS", "HT", "HS");
	ast_cli(fd, "----------------------------------------------------------------------------------------\n");
	exttmp = oh323_eplist;
	while (exttmp) {
		if (exttmp->ud.port > 0)
			snprintf(tmp, sizeof(tmp), "%s:%d", exttmp->ud.ipaddr, exttmp->ud.port);
		else
			snprintf(tmp, sizeof(tmp), "%s", exttmp->ud.ipaddr);
		ast_cli(fd, FORMAT_STRING2a,
					exttmp->name,
					tmp,
					exttmp->ud.username,
					exttmp->context,
					exttmp->ud.prefcodec < 0 ? "-" : ast_getformatname(oh323_codec2format(exttmp->ud.prefcodec)),
					exttmp->ud.faststart < 0 ? "-" : (exttmp->ud.faststart == 0 ? "OFF" : "ON"),
					exttmp->ud.h245tunnel < 0 ? "-" : (exttmp->ud.h245tunnel == 0 ? "OFF" : "ON"),
					exttmp->ud.h245insetup < 0 ? "-" : (exttmp->ud.h245insetup == 0 ? "OFF" : "ON"));
		exttmp = exttmp->next;
	}
	ast_cli(fd, "\n");
	ast_mutex_unlock(&oh323_eplock);
	return RESULT_SUCCESS;
}
static int oh323_debug_toggle(int fd, int argc, char *argv[])
{
	if (argc != 3)
		return RESULT_SHOWUSAGE;
	ast_mutex_lock(&oh323_tab_lock);
	if (oh323_verbose_debug != 0) {
		oh323_verbose_debug = 0;
		ast_cli(fd, "Verbose debug info for OpenH323 channel driver turned off.\n");
	} else {
		oh323_verbose_debug = 1;
		ast_cli(fd, "Verbose debug info for OpenH323 channel driver turned on.\n");
	}
	ast_mutex_unlock(&oh323_tab_lock);
	return RESULT_SUCCESS;
}
#if 0
static int oh323_mode(int fd, int argc, char *argv[])
{
	if (argc != 4)
		return RESULT_SHOWUSAGE;
	/*ast_mutex_lock(&oh323_tab_lock);*/
	h323_change_call(argv[2], argv[3]);
	/*ast_mutex_unlock(&oh323_tab_lock);*/
	return RESULT_SUCCESS;
}
#endif
static int oh323_vars(int fd, int argc, char *argv[])
{
	char *pvar, *pvar_val;
	int i, j, found = 0;

	if (argc == 3) {
		ast_mutex_lock(&oh323_tab_lock);
		for (i=0; i<config_options.totalNum; i++) {
			if (oh323_tab[i] != NULL) {
				found = 1;
				ast_cli(fd, "Variables for OpenH323 channel %s:\n",
								oh323_tab[i]->owner->name);
				j = 0;
				pvar = oh323_var_tab[j];
				while (pvar != NULL) {
					pvar_val = (char *)pbx_builtin_getvar_helper(oh323_tab[i]->owner, pvar);
					if (pvar_val)
						ast_cli(fd, "\t%s\t'%s'\n", pvar, pvar_val);
					pvar = oh323_var_tab[++j];
				}
			}
		}
		if (!found)
			ast_cli(fd, "No active H.323 connections\n");
		ast_mutex_unlock(&oh323_tab_lock);
		return RESULT_SUCCESS;
	} else
		return RESULT_SHOWUSAGE;
}
#if 0
static int oh323_reload(int fd, int argc, char *argv[])
{
	if (argc != 2)
		return RESULT_SHOWUSAGE;

	ast_mutex_lock(&oh323_reload_lock);
	if (oh323_reloading) {
		ast_cli(fd, "Previous reload not yet done.\n");
	} else
		oh323_reloading = 1;
	ast_mutex_unlock(&oh323_reload_lock);
	return RESULT_SUCCESS;
}
#endif

/* ----------- CLI extensions: Usage -------------- */
static char oh323_show_conf_usage[] =
"Usage: oh323 show conf\n"
"       Show configuration info and parameters of OpenH323 channel driver\n";
static char oh323_show_stats_usage[] =
"Usage: oh323 show stats\n"
"       Show various statistics of the operation of OpenH323 channel driver\n";
static char oh323_show_channels_usage[] =
"Usage: oh323 show channels\n"
"       Show verbose information about active OpenH323 channel(s)\n";
static char oh323_show_established_usage[] =
"Usage: oh323 show established\n"
"       Show the number of established H.323 channels\n";
static char oh323_show_ep_usage[] =
"Usage: oh323 show endpoints\n"
"       Show the configured H.323 endpoints\n";
static char oh323_debug_toggle_usage[] =
"Usage: oh323 debug toggle\n"
"       Toggle on/off (very) verbose debug info for OpenH323 channel driver\n";
/*static char oh323_mode_usage[] =
"Usage: oh323 mode\n"
"       Change the mode of a running OpenH323 channel\n"; */
static char oh323_vars_usage[] =
"Usage: oh323 show vars\n"
"       Show variables of running OpenH323 channels\n";
/* ----------- CLI extensions: Asterisk entries -------------- */
static struct ast_cli_entry	cli_show_conf = 
	{ { "oh323", "show", "conf", NULL }, oh323_show_conf, 
		"Show config info of OpenH323 channel driver", oh323_show_conf_usage };
static struct ast_cli_entry	cli_show_stats = 
	{ { "oh323", "show", "stats", NULL }, oh323_show_stats, 
		"Show statistics of OpenH323 channel driver", oh323_show_stats_usage };
static struct ast_cli_entry	cli_show_info = 
	{ { "oh323", "show", "channels", NULL }, oh323_show_channels, 
		"Show info about active OpenH323 channel(s)", oh323_show_channels_usage };
static struct ast_cli_entry	cli_show_established = 
	{ { "oh323", "show", "established", NULL }, oh323_show_established, 
		"Show the number of established H.323 channels", oh323_show_established_usage };
static struct ast_cli_entry	cli_show_ep = 
	{ { "oh323", "show", "endpoints", NULL }, oh323_show_ep, 
		"Show the configured H.323 endpoints", oh323_show_ep_usage };
static struct ast_cli_entry	cli_debug_toggle = 
	{ { "oh323", "debug", "toggle", NULL }, oh323_debug_toggle, 
		"Toggle on/off debug info for OpenH323 channel driver", oh323_debug_toggle_usage };
/*static struct ast_cli_entry	cli_mode = 
	{ { "oh323", "mode", NULL }, oh323_mode, 
		"Change the mode of a running OpenH323 channel", oh323_mode_usage };*/
static struct ast_cli_entry	cli_vars = 
	{ { "oh323", "show", "vars", NULL }, oh323_vars, 
		"Variables of running OpenH323 channels", oh323_vars_usage };

/******************************************************************************/
/* Helper functions */

unsigned int generate_uid(void)
{
	struct timeval tv;
	unsigned int res;

	ast_mutex_lock(&oh323_uid_lock);
	if (uid_base == 0) {
		if (gettimeofday(&tv, NULL) < 0) {
			ast_log(LOG_ERROR, "Failed to get time.\n");
			CRASH;
			ast_mutex_unlock(&oh323_uid_lock);
			return(0);
		}
		uid_base = ((0xff & tv.tv_sec) << 24) | ((0xff & tv.tv_usec) << 16) | (0xffff & rand());
	} else {
		uid_base++;
	}
	res = uid_base;
	ast_mutex_unlock(&oh323_uid_lock);
	return(res);
}

static char *get_str(num_str_t tab[], int num)
{
	int i = 0;
	while (1) {
		if ((tab[i].num < 0) || (tab[i].num == num))
			break;
		i++;
	}
	return(tab[i].str);
}

static int get_num(num_str_t tab[], char *str)
{
	int i = 0;
	while (1) {
		if ((tab[i].num < 0) || (!strcasecmp(tab[i].str, str)))
			break;
		i++;
	}
	return(tab[i].num);
}

/**
 * Search the list of configured H.323 endpoints and return a pointer
 * to its structure, if found. Returns, NULL if not found. It is up to
 * the caller to lock or not the list's mutex.
 */
static struct oh323_ep *find_oh323_ep(char *epname, char *host, char *user)
{
	struct oh323_ep *tmp = oh323_eplist;
	if (epname != NULL) {
		while (tmp) {
			if (!strcmp(tmp->name, epname))
				return(tmp);
			tmp = tmp->next;
		}
		return(NULL);
	}
	if (host != NULL) {
		while (tmp) {
			if (!strcmp(tmp->ud.ipaddr, host)) {
				if (user != NULL) {
					if (!strcmp(tmp->ud.username, user))
						return(tmp);
				} else {
					return(tmp);
				}
			}
			tmp = tmp->next;
		}
		return(NULL);
	}
	return(NULL);
}

/**
 * Parse a OH323 dialstring into exten/peer/port parts. The dialstring
 * has the following forms:
 *   [exten@]peer[:port]
 * or
 *   peer[:port][/exten]
 *
 * If the dialstring has only one part then if this is a valid phone number,
 * it is returned as 'exten' and not as 'peer'.
 * It modifies the 'ds' buffer.
 */
static void parse_dial_string(char *ds, char **exten, char **peer, char **port)
{
	char tmp_buf[512]= { 0 };

	snprintf(tmp_buf, sizeof(tmp_buf), "%s", ds);
	*peer = strchr(ds, '@');
	if (*peer) {
		**peer = '\0';
		(*peer)++;
		*exten = ds;
	} else {
		*exten = strchr(ds, '/');
		if (*exten) {
			**exten = '\0';
			(*exten)++;
			*peer = ds;
		} else {
			*peer = ds;
			*exten = NULL;
		}
	}
	*port = strchr(*peer, ':');
	if (*port) {
		**port = '\0';
		(*port)++;
	} else {
		*port = NULL;
	}
	if ((*exten == NULL) && (*port == NULL) && (ast_isphonenumber(*peer))) {
		*exten = *peer;
		*peer = NULL;
	}
#if 0
	ast_log(LOG_DEBUG, "Parsed dialstring '%s' in peer='%s', ext='%s', port='%s'\n",
					tmp_buf, *peer, *exten, *port);
#endif
}

/**
 * Build a callable H.323 address based either on explicit data (ext, peer, port) or
 * pre-configured endpoint info (ext, ep).
 */
static char *build_address(char *addr, int addr_len, char *ext, char *peer, char *port,
				struct oh323_ep *ep)
{
	char tmp[512];

	memset(tmp, 0, sizeof(tmp));
	if (ep == NULL) {
		if (ext) {
			if (peer) {
				if (port)
					snprintf(tmp, sizeof(tmp), "%s@%s:%s", ext, peer, port);
				else
					snprintf(tmp, sizeof(tmp), "%s@%s", ext, peer);
			} else {
				snprintf(tmp, sizeof(tmp), "%s", ext);
			}
		} else {
			if (peer) {
				if (port)
					snprintf(tmp, sizeof(tmp), "%s:%s", peer, port);
				else
					snprintf(tmp, sizeof(tmp), "%s", peer);
			}
		}
	} else {
		if (ext) {
			if (strlen(ep->ud.ipaddr) > 0) {
				if (ep->ud.port > 0)
					snprintf(tmp, sizeof(tmp), "%s@%s:%d", ext, ep->ud.ipaddr, ep->ud.port);
				else
					snprintf(tmp, sizeof(tmp), "%s@%s", ext, ep->ud.ipaddr);
			} else {
				snprintf(tmp, sizeof(tmp), "%s", ext);
			}
		} else if (strlen(ep->ud.username) > 0) {
			if (strlen(ep->ud.ipaddr) > 0) {
				if (ep->ud.port > 0)
					snprintf(tmp, sizeof(tmp), "%s@%s:%d", ep->ud.username, ep->ud.ipaddr, ep->ud.port);
				else
					snprintf(tmp, sizeof(tmp), "%s@%s", ep->ud.username, ep->ud.ipaddr);
			} else {
				snprintf(tmp, sizeof(tmp), "%s", ep->ud.username);
			}
		} else {
			if (strlen(ep->ud.ipaddr) > 0) {
				if (ep->ud.port > 0)
					snprintf(tmp, sizeof(tmp), "%s:%d", ep->ud.ipaddr, ep->ud.port);
				else
					snprintf(tmp, sizeof(tmp), "%s", ep->ud.ipaddr);
			}
		}
	}
	if (strlen(tmp) < addr_len-1) {
		strncpy(addr, tmp, addr_len);
		return(addr);
	} else
		return(NULL);
}

/******************************************************************************/
/* Channel driver API functions */

/**
 * Handle exceptions of the OpenH323 channel.
 */
static struct ast_frame *oh323_exception(struct ast_channel *c)
{
	struct chan_oh323_pvt *p = (struct chan_oh323_pvt *)c->tech_pvt;
	char notify_buf[2] = { OH323_EVENT_NTFY };
	char *p1, *p2;
	struct exception_oh323 *e;

	MARK_IN();
	ast_mutex_lock(&oh323_tab_lock);

	/* Some nice norms */
	p->fr.datalen = 0;
	p->fr.data =  NULL;
	p->fr.src = type;
	p->fr.offset = 0;
	p->fr.mallocd = 0;
	p->fr.src = __FUNCTION__;

	/* Exctract exception data from list */
	OBJLIST_EXTRACT(&(p->exceptl), e);
	if (!e) {
		ast_log(LOG_ERROR, "%s: %s called but exception list is empty!\n", c->name, __FUNCTION__);
		CRASH;
	}

	/* -- User input */
	if (e->type == OH323EXC_USER_INPUT_TONE) {
		if (e->data[0] == '!') {
			p->fr.frametype = AST_FRAME_CONTROL;
			p->fr.subclass = AST_CONTROL_FLASH;
			if (option_debug)
				ast_log(LOG_DEBUG, "%s: Got flash hook.\n", c->name);
		} else if (e->data[0] == ' ') {
			p->fr.frametype = AST_FRAME_NULL;
			p->fr.subclass = 0;
		} else {
			p->fr.frametype = AST_FRAME_DTMF;
			p->fr.subclass = e->data[0];
			if (option_debug)
				ast_log(LOG_DEBUG, "%s: Got DTMF %c.\n", c->name, 
								p->fr.subclass);
		}
	/* -- User text message */
	} else if (e->type == OH323EXC_USER_MESSAGE) {
		p->fr.frametype = AST_FRAME_TEXT;
		p->fr.subclass = 0;
		memset(p->txtbuf, 0, sizeof(p->txtbuf));
		strncpy((char*)(p->txtbuf + AST_FRIENDLY_OFFSET), e->data,
							sizeof(p->txtbuf) - AST_FRIENDLY_OFFSET - 1);
		p->fr.data = p->txtbuf + AST_FRIENDLY_OFFSET;
		p->fr.offset = AST_FRIENDLY_OFFSET;
	/* -- Call alerted (remote endpoint is ringing) */
	} else if (e->type == OH323EXC_CALL_ALERTED) {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Call alerted.\n", c->name);
		p->fr.frametype = AST_FRAME_CONTROL;
		p->fr.subclass = AST_CONTROL_RINGING;
	/* -- Call has progress information (inband announcements/tones) */
	} else if (e->type == OH323EXC_CALL_PROGRESS) {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Call progress.\n", c->name);
		p->fr.frametype = AST_FRAME_CONTROL;
		p->fr.subclass = AST_CONTROL_PROGRESS;
	/* -- Call transfer */
	} else if (e->type == OH323EXC_CALL_TRANSFER) {
		/* XXX Asterisk MUST read first the frame returned by this exception
		 *     and then the queued DTMFs (the extension to transfer to) */
		p->fr.frametype = AST_FRAME_DTMF;
		p->fr.subclass = '#';
		if (strlen(e->data) > 0) {
			if (option_debug)
				ast_log(LOG_DEBUG, "%s: Native transfer to '%s'.\n", c->name, e->data);
			memset(c->unused_old_dtmfq, 0, sizeof(c->unused_old_dtmfq));
			memcpy(c->unused_old_dtmfq, e->data, strlen(e->data));
			write(p->event_pipe[1], notify_buf, 1);
		}
	/* -- Call establishment notification */
	} else if (e->type == OH323EXC_CALL_ESTABLISHED) {
		memset(p->rtp.local_addr, 0, sizeof(p->rtp.local_addr));
		memset(p->rtp.remote_addr, 0, sizeof(p->rtp.remote_addr));
		p1 = e->data;
		p2 = strchr(e->data, ':');
		if (p2) {
			p2 = strchr(p2, '-');
			if (p2) {
				p2[0] = '\0';
				++p2;
				strncpy(p->rtp.local_addr, p1, sizeof(p->rtp.local_addr)-1);
				strncpy(p->rtp.remote_addr, p2, sizeof(p->rtp.remote_addr)-1);
			} else
				ast_log(LOG_WARNING, "%s: Invalid format of RTP addresses. No common codecs???\n",
							c->name);
		} else
			ast_log(LOG_WARNING, "%s: Invalid format of RTP addresses. No common codecs???\n",
						c->name);
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Local RTP '%s', Remote RTP '%s'.\n",
						c->name, p->rtp.local_addr, p->rtp.remote_addr);
		p->established = 1;
		if (c->_state != AST_STATE_UP) {
			if (option_debug)
				ast_log(LOG_DEBUG, "%s: Channel is going UP.\n", c->name);
			p->fr.frametype = AST_FRAME_CONTROL;
			p->fr.subclass = AST_CONTROL_ANSWER;
			//ISTATE_LOG(p->i_state, OH323_STATE_ESTABLISHED);
			//p->i_state = OH323_STATE_ESTABLISHED;
		} else {
			if (p->i_state != OH323_STATE_ESTABLISHED)
				ast_log(LOG_ERROR, "%s: Got ESTABLISHED while UP and %s!\n", 
								c->name, get_str(state_str, p->i_state));
			p->fr.frametype = AST_FRAME_NULL;
			p->fr.subclass = 0;
			if (option_debug)
				ast_log(LOG_DEBUG, "%s: Got ESTABLISHED while UP!\n", c->name);
		}
	/* -- Call clearing notification */
	} else if (e->type == OH323EXC_CALL_CLEARED) {
		/* Handle the clearing of the call */
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Call cleared.\n", c->name);
		switch (h323_get_reason_code(p->cd.call_end_reason)) {
			case OH323END_NO_ACCEPT:
			case OH323END_REFUSAL:
			case OH323END_NO_ANSWER:
			case OH323END_ANSWER_DENIED:
			case OH323END_LOCAL_BUSY:
			case OH323END_REMOTE_BUSY:
			case OH323END_NO_ENDPOINT:
			case OH323END_CONNECT_FAIL:
			case OH323END_HOST_OFFLINE:
			case OH323END_Q931CAUSE:
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Generating BUSY.\n", c->name);
				p->fr.frametype = AST_FRAME_CONTROL;
				p->fr.subclass = AST_CONTROL_BUSY;
				break;
			case OH323END_TRANSPORT_FAIL:
			case OH323END_GATEKEEPER:
			case OH323END_NO_USER:
			case OH323END_NO_BANDWIDTH:
			case OH323END_SECURITY:
			case OH323END_LOCAL_CONGESTION:
			case OH323END_UNREACHABLE:
			case OH323END_TEMP_FAILURE:
			case OH323END_DURATION_LIMIT:
			case OH323END_INVALIDCID:
			case OH323END_REMOTE_CONGESTION:
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Generating CONGESTION.\n", c->name);
				p->fr.frametype = AST_FRAME_CONTROL;
				p->fr.subclass = AST_CONTROL_CONGESTION;
				break;
			case OH323END_LOCAL_USER:
			case OH323END_REMOTE_USER:
			case OH323END_CALLER_ABORT:
			case OH323END_CALLFWD:
			case OH323END_CAPABILITY:
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Generating HANGUP.\n", c->name);
				p->fr.frametype = AST_FRAME_CONTROL;
				p->fr.subclass = AST_CONTROL_HANGUP;
				break;
			default:
				ast_log(LOG_WARNING, "%s: Call cleared with unknown reason (%d).\n",
								c->name,
								h323_get_reason_code(p->cd.call_end_reason));
				OBJ_FREE(e);
				ast_mutex_unlock(&oh323_tab_lock);
				MARK_OUT();
				return(NULL);
		}
	/* -- Call clearing notification */
	} else if (e->type == OH323EXC_CTRL_ERROR) {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Got protocol error (%s).\n", c->name, e->data);
		ast_queue_hangup(c);
		OBJ_FREE(e);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(NULL);
	/* -- Unknown exception */
	} else {
		/*p->fr.frametype = AST_FRAME_NULL;*/
		/*p->fr.subclass = 0;*/
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Got null/unknown exception.\n", c->name);
		OBJ_FREE(e);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(NULL);
	}
	OBJ_FREE(e);
	ast_mutex_unlock(&oh323_tab_lock);
	MARK_OUT();
	return(&(p->fr));
}

/**
 * Indicate various conditions on a channel.
 * Return -1 on error, 0 on success.
 */
static int oh323_indicate(struct ast_channel *c, int condition, const void *data, size_t datalen)
{
	struct chan_oh323_pvt *pvt = (struct chan_oh323_pvt *)c->tech_pvt;
	int res;
	int do_indicate = -1;
	struct request_oh323 *e;

	if (option_debug)
		ast_log(LOG_DEBUG, "%s: Indicating condition %d.\n", c->name, condition);

	ast_mutex_lock(&oh323_tab_lock);
	switch (condition) {
		case AST_CONTROL_RINGING:
			if ((c->_state == AST_STATE_RING) && (pvt->progress == 0)) {
				do_indicate = IND_RINGING;
				break;
			}
			ast_mutex_unlock(&oh323_tab_lock);
			return(-1);
		case AST_CONTROL_BUSY:
			if (c->_state != AST_STATE_UP) {
				do_indicate = IND_BUSY;
				break;
			}
			ast_mutex_unlock(&oh323_tab_lock);
			return(-1);
		case AST_CONTROL_CONGESTION:
			if (c->_state != AST_STATE_UP) {
				do_indicate = IND_CONGESTION;
				break;
			}
			ast_mutex_unlock(&oh323_tab_lock);
			return(-1);
		case AST_CONTROL_PROGRESS:
			if (c->_state != AST_STATE_UP) {
				pvt->progress = 1;
				do_indicate = IND_PROGRESS;
				break;
			}
			ast_mutex_unlock(&oh323_tab_lock);
			return(-1);
		case -1:
			res = -1;
			break;
		default:
			ast_log(LOG_WARNING, "Don't know how to indicate condition %d\n", 
							condition);
			res = -1;
			break;
	}
	if (do_indicate > -1) {
		MYMARK("Indicating condition");
		OBJ_ALLOC(struct request_oh323, e);
		e->type = REQ_INDICATE;
		strncpy((e->indication).token, (pvt->cd).call_token, sizeof((e->indication).token)-1);
		(e->indication).type = do_indicate;
		OBJLIST_INSERT(&requestl, e);
		ast_sched_add(oh323_sched, DEFAULT_REQTIME, oh323_exec_request, NULL);
		res = 0;
	}
	ast_mutex_unlock(&oh323_tab_lock);
	return(res);
}

/**
 * Send (play) the specified digit to the channel.
 * Return -1 on error, 0 on success.
 */
static int oh323_digit(struct ast_channel *c, char digit, unsigned int duration)
{
	struct chan_oh323_pvt *pvt = (struct chan_oh323_pvt *)c->tech_pvt;
	struct request_oh323 *e;

	if (option_debug)
		ast_log(LOG_DEBUG, "In oh323_digit (%s, dig=%c).\n", c->name, digit);

	ast_mutex_lock(&oh323_tab_lock);
	if (c->_state != AST_STATE_UP) {
		ast_log(LOG_WARNING, "%s: Channel is not UP!\n", c->name);
		ast_mutex_unlock(&oh323_tab_lock);
		return(-1);
	}
	MYMARK("Sending digit");
	OBJ_ALLOC(struct request_oh323, e);
	e->type = REQ_DIGIT;
	strncpy((e->digit).token, (pvt->cd).call_token, sizeof((e->digit).token)-1);
	(e->digit).digit = digit;
	OBJLIST_INSERT(&requestl, e);
	ast_sched_add(oh323_sched, DEFAULT_REQTIME, oh323_exec_request, NULL);

	ast_mutex_unlock(&oh323_tab_lock);
	return(0);
}

/**
 * Send text message to the channel.
 * Return -1 on error, 0 on success.
 */
static int oh323_text(struct ast_channel *c, const char *text)
{
	struct chan_oh323_pvt *pvt = (struct chan_oh323_pvt *)c->tech_pvt;
	struct request_oh323 *e;

	if (option_debug)
		ast_log(LOG_DEBUG, "In oh323_text (%s, text='%s').\n", c->name, text);

	ast_mutex_lock(&oh323_tab_lock);
	if (c->_state != AST_STATE_UP) {
		ast_log(LOG_WARNING, "%s: Channel is not UP!\n", c->name);
		ast_mutex_unlock(&oh323_tab_lock);
		return(-1);
	}
	MYMARK("Sending text");
	OBJ_ALLOC(struct request_oh323, e);
	e->type = REQ_TEXT;
	strncpy((e->text).token, (pvt->cd).call_token, sizeof((e->text).token)-1);
	strncpy((e->text).text, text, sizeof((e->text).text)-1);
	OBJLIST_INSERT(&requestl, e);
	ast_sched_add(oh323_sched, DEFAULT_REQTIME, oh323_exec_request, NULL);
 
	ast_mutex_unlock(&oh323_tab_lock);
	return(0);
}

/**
 * Make a call over the specified channel to the specified 
 * destination. This function will parse the destination string
 * and determine the address-number to call.
 * Return -1 on error, 0 on success.
 */
static int oh323_call(struct ast_channel *c, char *dest, int timeout)
{
	struct chan_oh323_pvt *pvt = (struct chan_oh323_pvt *)c->tech_pvt;
	char *tmpbuf;
	char *out_codec;
	int fmt;
	char *ext, *peer, *port;
	char tmp[256] = "";
	struct oh323_ep *ptr;
	struct request_oh323 *e;
	unsigned int id;

	if (option_debug)
		ast_log(LOG_DEBUG, "In oh323_call (%s, dest=%s, timeout=%d).\n", 
						c->name, dest, timeout);

	ast_mutex_lock(&oh323_tab_lock);

	if ((c->_state != AST_STATE_DOWN) && (c->_state != AST_STATE_RESERVED)) {
		ast_log(LOG_WARNING, "%s: Channel is already in use?\n", c->name);
		ast_mutex_unlock(&oh323_tab_lock);
		return(-1);
	}
	id = pvt->ep_settings.ud.app_id;

	/* Build the address to call. */
	memset(pvt->called_addr, 0, sizeof(pvt->called_addr));
	strncpy(tmp, dest, sizeof(tmp) - 1);
	parse_dial_string(tmp, &ext, &peer, &port);

	/* Is this a configured endpoint? */
	ast_mutex_lock(&oh323_eplock);
	ptr = find_oh323_ep(peer, NULL, NULL);
	if (ptr != NULL) {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Target '%s' is a configured H.323 endpoint\n", c->name, peer);
		build_address(pvt->called_addr, sizeof(pvt->called_addr), ext, NULL, NULL, ptr);
		pvt->ep_settings = *ptr;
		pvt->ep_settings.ud.incoming_call = 0;
		pvt->ep_settings.ud.app_id = id;
		if (strlen(c->exten) > 0)
			strncpy(pvt->ep_settings.ud.called_num, c->exten, sizeof(pvt->ep_settings.ud.called_num) - 1);
		if ((c->cid.cid_dnid) && (strlen(c->cid.cid_dnid) > 0))
			strncpy(pvt->ep_settings.ud.dnid, c->cid.cid_dnid, sizeof(pvt->ep_settings.ud.dnid) - 1);
		if ((c->cid.cid_rdnis) && (strlen(c->cid.cid_rdnis) > 0))
			strncpy(pvt->ep_settings.ud.redirect_num, c->cid.cid_rdnis, sizeof(pvt->ep_settings.ud.redirect_num) - 1);
		ast_mutex_unlock(&oh323_eplock);
	} else {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Target '%s' is not a configured H.323 endpoint\n", c->name, peer);
		ast_mutex_unlock(&oh323_eplock);
		build_address(pvt->called_addr, sizeof(pvt->called_addr), ext, peer, port, NULL);
		pvt->ep_settings.ud.incoming_call = 0;
		pvt->ep_settings.ud.app_id = id;
		pvt->ep_settings.name[0] = '\0';
		pvt->ep_settings.ud.prefdtmf = -1;
		if ((c->cid.cid_name) && (strlen(c->cid.cid_name) > 0))
			strncpy(pvt->ep_settings.ud.username, c->cid.cid_name, sizeof(pvt->ep_settings.ud.username) - 1);
		if ((c->cid.cid_num) && (strlen(c->cid.cid_num) > 0))
			strncpy(pvt->ep_settings.ud.calling_num, c->cid.cid_num, sizeof(pvt->ep_settings.ud.calling_num) - 1);
		if (strlen(c->exten) > 0)
			strncpy(pvt->ep_settings.ud.called_num, c->exten, sizeof(pvt->ep_settings.ud.called_num) - 1);
		if ((c->cid.cid_dnid) && (strlen(c->cid.cid_dnid) > 0))
			strncpy(pvt->ep_settings.ud.dnid, c->cid.cid_dnid, sizeof(pvt->ep_settings.ud.dnid) - 1);
		if ((c->cid.cid_rdnis) && (strlen(c->cid.cid_rdnis) > 0))
			strncpy(pvt->ep_settings.ud.redirect_num, c->cid.cid_rdnis, sizeof(pvt->ep_settings.ud.redirect_num) - 1);
		pvt->ep_settings.ud.prefcodec = -1;
		if (peer)
			strncpy(pvt->ep_settings.ud.ipaddr, peer, sizeof(pvt->ep_settings.ud.ipaddr) - 1);
		if (ext)
			strncpy(pvt->ep_settings.ud.called_num, ext, sizeof(pvt->ep_settings.ud.called_num) - 1);
	}

	/* Set the codec according to the value of the OH323_OUTCODEC
	 * variable overriding the configuration file. */
	out_codec = (char *)pbx_builtin_getvar_helper(c, "OH323_OUTCODEC");
	if (out_codec) {
		fmt = ast_getformatbyname(out_codec);
		oh323_format2codecset(fmt, &(pvt->ep_settings.ud.prefcodec), 1);
		if (pvt->ep_settings.ud.prefcodec == 0) {
			ast_log(LOG_ERROR, "%s: Unsupported ${OH323_OUTCODEC} value (%s)!\n", 
							c->name, out_codec);
			ast_mutex_unlock(&oh323_tab_lock);
			return(-1);
		}
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Set codec according to channel's "
						"${OH323_OUTCODEC} (%s)\n", c->name, out_codec);
		if (option_verbose > 2)
			ast_verbose(VERBOSE_PREFIX_3 "H.323 call to %s with codec %s\n",
							dest, ast_getformatname(fmt));
	} else {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: No ${OH323_OUTCODEC}.\n", c->name);
		tmpbuf = NULL;
		if (option_verbose > 2)
			ast_verbose(VERBOSE_PREFIX_3 "H.323 call to %s with codec(s) %s\n",
					dest, oh323_codecset2str(0, config_options.codecInfo, &tmpbuf));
		if (tmpbuf != NULL)
			free(tmpbuf);
	}

	memset((char *)(pvt->cd).call_token, 0, sizeof((pvt->cd).call_token));
	ISTATE_LOG(pvt->i_state, OH323_STATE_INIT);
	pvt->i_state = OH323_STATE_INIT;

	MYMARK("Making call");
	OBJ_ALLOC(struct request_oh323, e);
	e->type = REQ_CALL;
	strncpy((e->call).addr, pvt->called_addr, sizeof((e->call).addr)-1);
	(e->call).cd = pvt->cd;
	(e->call).ud = (pvt->ep_settings).ud;
	OBJLIST_INSERT(&requestl, e);
	ast_sched_add(oh323_sched, DEFAULT_REQTIME, oh323_exec_request, NULL);

	if (option_verbose > 2)
		ast_verbose(VERBOSE_PREFIX_3 "Outbound H.323 call to destination '%s', channel '%s'.\n",
						pvt->called_addr, c->name);

	/* Set the state of the channel and fix the name */
	ast_setstate(c, AST_STATE_DIALING);

	/* The call initiated successfully */
	if (option_debug)
		ast_log(LOG_DEBUG, "%s: Call to %s initiated successfully.\n", 
						c->name, pvt->called_addr);
	ast_mutex_unlock(&oh323_tab_lock);

	return(0);
}

/**
 * Send a hangup to the specified channel. This function
 * will terminate the active call over this channel, will
 * decrease the usage counter and will free allocated resources.
 * It can not fail !!!
 */
static int oh323_hangup(struct ast_channel *c) 
{
	struct chan_oh323_pvt *pvt = (struct chan_oh323_pvt *)c->tech_pvt;
	int index;
	struct request_oh323 *e;

	if (option_debug)
		ast_log(LOG_DEBUG, "In oh323_hangup (%s).\n", c->name);

	ast_mutex_lock(&oh323_tab_lock);
	if (pvt) {
		ast_setstate(c, AST_STATE_DOWN);
		ast_queue_hangup(c);

		ISTATE_LOG(pvt->i_state, OH323_STATE_CLEARED);
		pvt->i_state = OH323_STATE_CLEARED;
		index = find_call(pvt->cd.call_token, pvt->ep_settings.ud.app_id);
		if (index < 0) {
			ast_log(LOG_NOTICE, "%s: Cannot hangup a call which doesn't exist.\n", c->name);
			pvt->needs_destroy = 1;
		} else {
			/* Send the hangup only on local hangups */
			if (!(pvt->clearing_call)) {
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Hanging up call '%s'.\n", c->name, (pvt->cd).call_token);
				pvt->clearing_call = OH323_CLEAR_LOC;
				/* Make sure that resources of the call will be released */
				if ((index >= 0) && (index < config_options.totalNum)) {
					pvt->index = index;
					pvt->sched_id = ast_sched_add(oh323_sched, 25000, oh323_release, pvt);
				}
				MYMARK("Hanging up call");
				OBJ_ALLOC(struct request_oh323, e);
				e->type = REQ_HANGUP;
				strncpy((e->hangup).token, (pvt->cd).call_token, sizeof((e->hangup).token)-1);
				if (c->hangupcause)
					(e->hangup).q931_cause = c->hangupcause;
				OBJLIST_INSERT(&requestl, e);
				ast_sched_add(oh323_sched, DEFAULT_REQTIME, oh323_exec_request, NULL);
			} else {
				/* Resources will be freed by the monitor thread */
				pvt->needs_destroy = 1;
			}
		}

		/* No match found ?!?!?! */
		if ((index<0) || (index>=config_options.totalNum)) {
			ast_log(LOG_WARNING, "%s: Call '%s' not found (hangup)!\n", 
							c->name, pvt->cd.call_token);
		} else {
			if (option_debug)
				ast_log(LOG_DEBUG, "%s: Call '%s' found in %d (hangup).\n", 
								c->name, pvt->cd.call_token, index);
		}

		/* Disconnect channel and pvt */
		c->tech_pvt = NULL;
		pvt->owner = NULL;

		/* Update usage counter */
		ast_mutex_lock(&usecnt_lock);
		usecnt--;
		if (usecnt < 0) 
	 		ast_log(LOG_WARNING, "Usecnt < 0???\n");
		ast_mutex_unlock(&usecnt_lock);
		ast_update_use_count();
		if (option_verbose > 2) 
			ast_verbose( VERBOSE_PREFIX_3 "Hungup '%s'\n", c->name);
	} else {
		ast_log(LOG_WARNING, "Channel '%s' has no private structure!\n", c->name);
	}
	ast_mutex_unlock(&oh323_tab_lock);

	return(0);
}

/**
 * Read data from the network, encapsulate them in a frame
 * and return it to Asterisk PBX.
 * Return NULL on error, a frame pointer on success.
 */
static struct ast_frame *oh323_read(struct ast_channel *c)
{
	struct chan_oh323_pvt *p = (struct chan_oh323_pvt *)c->tech_pvt;
	int res, read_size, framenum;
	struct ast_frame *f;
	char buf[100];
	int valid_g729_frame, i;

	ast_mutex_lock(&oh323_tab_lock);

	/* Do this basic initialization here */
	p->fr.mallocd = 0;
	p->fr.datalen = 0;
	p->fr.data = p->buf + AST_FRIENDLY_OFFSET;
	p->fr.src = type;
	p->fr.offset = AST_FRIENDLY_OFFSET;
	p->fr.samples = 0;
	p->fr.src = __FUNCTION__;

	/* Check the event pipe */
	if ((res = read(p->event_pipe[0], buf, 1)) == 1) {
		switch (buf[0]) {
			case OH323_EVENT_FDUPDATE:
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Channel needs to update its fds.\n", c->name);
				if (p->new_player_fd > -1) {
					if (p->player_fd[0] > -1)
						close(p->player_fd[0]);
					p->player_fd[0] = p->new_player_fd;
					c->fds[1] = p->new_player_fd;
					p->normread = 0;
					p->rxcount = 0;
					ast_log(LOG_DEBUG, "Updated player fd.\n");
				}
				if (p->new_recorder_fd > -1) {
					if (p->recorder_fd[0] > -1)
						close(p->recorder_fd[0]);
					p->recorder_fd[0] = p->new_recorder_fd;
					p->txcount = 0;
					ast_log(LOG_DEBUG, "Updated recorder fd.\n");
				}
				break;

			//case OH323_EVENT_TERM:
			case OH323_EVENT_EXCE:
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Channel has generated an exception.\n", 
									c->name);
				ast_mutex_unlock(&oh323_tab_lock);
				return oh323_exception(c);

			case OH323_EVENT_NTFY:
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Channel has been notified.\n", 
									c->name);
				p->fr.frametype = AST_FRAME_NULL;
				p->fr.subclass = 0;
				break;

			default:
				ast_log(LOG_DEBUG, "%s: Unknown event type (%d).\n", 
									c->name, buf[0]);
				ast_mutex_unlock(&oh323_tab_lock);
				return(NULL);
		}
	} else {
		if ((res < 0) && ((errno != EAGAIN)&&(errno != EINTR)))
			ast_log(LOG_WARNING, "%s: Failed to read from event pipe (%s).\n",
							c->name, strerror(errno));
	}

	/* Find out how much data we are going to read */
	if (p->rx_buf_size <= 0) {
		read_size = OH323_MAX_BUF;
	} else {
		if (p->rx_buf_size > OH323_MAX_BUF) {
			ast_log(LOG_WARNING, "%s: Requested read buffer size is too long (%d)!\n",
							c->name, p->rx_buf_size);
			ast_log(LOG_WARNING, "%s: Truncating to %d.\n", c->name, OH323_MAX_BUF);
			read_size = OH323_MAX_BUF;
		} else
			read_size = p->rx_buf_size;
	}

	/* Try to read some data... */
	if (!p->normread) {	/* First-time actions */
		res = 0;
		while (read(p->player_fd[0], (char *)(p->buf + AST_FRIENDLY_OFFSET), 1) > 0) res++;
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Reading for the first time. Removed %d bytes.\n", 
							c->name, res);
		p->normread = 1;
		p->fr.frametype = AST_FRAME_NULL;
		p->fr.subclass = 0;
		p->fr.offset = AST_FRIENDLY_OFFSET;
		ast_mutex_unlock(&oh323_tab_lock);
		return &(p->fr);
	}
	/* Normal read */
	res = read(p->player_fd[0], (char *)(p->buf + AST_FRIENDLY_OFFSET), read_size);
	if ((res < 0) && ((errno == EAGAIN)||(errno == EINTR))) {
		/* No error */
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: EAGAIN/EINTR on channel.\n", c->name);
		p->fr.frametype = AST_FRAME_NULL;
		p->fr.subclass = 0;
		p->fr.offset = AST_FRIENDLY_OFFSET;
		ast_mutex_unlock(&oh323_tab_lock);
		return &(p->fr);
	}
	if (res <= 0) {
		if (option_debug) {
			if (res < 0)
				ast_log(LOG_NOTICE, "%s: Error reading from channel (%s).\n", 
								c->name, strerror(errno));
			else
				ast_log(LOG_NOTICE, "%s: Read zero bytes.\n", c->name);
		}
		c->fds[1] = -1;	/* Remove the fd from the channel's fds. */
		p->fr.frametype = AST_FRAME_NULL;
		p->fr.subclass = 0;
		p->fr.offset = AST_FRIENDLY_OFFSET;
		ast_mutex_unlock(&oh323_tab_lock);
		return &(p->fr);
	}

	p->rxcount += res;
	if (oh323_verbose_debug && (p->rxcount % (100 * p->rx_buf_size) < p->rx_buf_size))
		ast_verbose("Channel %s (call '%s') RX byte count is %d.\n", 
					c->name, p->cd.call_token, p->rxcount);

	/* Check for format changes */
	if (p->last_rx_format != p->capability) {
		ast_log(LOG_NOTICE, "%s: Format changed from %s to %s.\n", 
						c->name, 
						ast_getformatname(p->last_rx_format), 
						ast_getformatname(p->capability));
		p->last_rx_format = p->capability;
	}

	/* Do specific stuff for each format type */
	switch (p->capability) {
		case 0:
			p->fr.frametype = AST_FRAME_NULL;
			p->fr.subclass = 0;
			ast_mutex_unlock(&oh323_tab_lock);
			return &(p->fr);

		case AST_FORMAT_ULAW:
		case AST_FORMAT_ALAW:
			p->fr.samples = res;
			break;

		case AST_FORMAT_GSM:
			if (res % 33 != 0) {
				ast_log(LOG_WARNING, "%s: Invalid size for GSM (%d bytes).\n", 
						c->name, res);
				p->fr.frametype = AST_FRAME_NULL;
				p->fr.subclass = 0;
				p->fr.offset = AST_FRIENDLY_OFFSET;
				ast_mutex_unlock(&oh323_tab_lock);
				return &(p->fr);
			}
			/* Check for silence */
			if (((char *)(p->buf + AST_FRIENDLY_OFFSET))[0] == 0) {
				p->fr.frametype = AST_FRAME_NULL;
				p->fr.subclass = 0;
				p->fr.offset = AST_FRIENDLY_OFFSET;
				ast_mutex_unlock(&oh323_tab_lock);
				return &(p->fr);
			}
			p->fr.samples = 160 * (res / 33);
			break;

		case AST_FORMAT_G729A:
			if (res % 10 != 0) {
				ast_log(LOG_WARNING, "%s: Invalid size for G.729 (%d bytes).\n", 
						c->name, res);
				p->fr.frametype = AST_FRAME_NULL;
				p->fr.subclass = 0;
				p->fr.offset = AST_FRIENDLY_OFFSET;
				ast_mutex_unlock(&oh323_tab_lock);
				return &(p->fr);
			}
			/* Drop zeroed G.729 frames */
			valid_g729_frame = 0;
			for (i = 0; i < res; i++) {
				if (((char*)(p->buf + AST_FRIENDLY_OFFSET))[i] != '\0') {
					valid_g729_frame = 1;
					break;
				}
			}
			if (!valid_g729_frame) {
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Dropped zeroed G.729 frame.\n",
									c->name);
				p->fr.frametype = AST_FRAME_NULL;
				p->fr.subclass = 0;
				p->fr.offset = AST_FRIENDLY_OFFSET;
				ast_mutex_unlock(&oh323_tab_lock);
				return &(p->fr);
			}

			p->fr.samples = 80 * (res / 10);
			break;

		case AST_FORMAT_G723_1:
			/* Create the smoother, if not already there */
			if (p->rx_g7231_smooth == NULL) {
				p->rx_g7231_smooth = G7231SF_new();
				if (p->rx_g7231_smooth == NULL) {
					ast_log(LOG_ERROR, "%s: Failed to create G.723.1 smoother.\n", c->name);
					p->fr.frametype = AST_FRAME_NULL;
					p->fr.subclass = 0;
					p->fr.offset = AST_FRIENDLY_OFFSET;
					ast_mutex_unlock(&oh323_tab_lock);
					return &(p->fr);
				}
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Created G.723.1 smoother.\n", c->name);
			}

			/* Push the data */
			p->fr.samples = 0;
			if (G7231SF_push(p->rx_g7231_smooth, (p->buf + AST_FRIENDLY_OFFSET), res) < 0) {
				p->fr.frametype = AST_FRAME_NULL;
				p->fr.subclass = 0;
				p->fr.offset = AST_FRIENDLY_OFFSET;
				ast_mutex_unlock(&oh323_tab_lock);
				return &(p->fr);
			}
#ifdef	DEBUG_CIRCBUFFER
			ast_log(LOG_DEBUG, "Checked-in %d bytes.\n", res);
#endif

			/* Checkout a whole number of frames */
			framenum = -1;
			res = G7231SF_pop(p->rx_g7231_smooth, (p->buf + AST_FRIENDLY_OFFSET), sizeof(p->buf) - AST_FRIENDLY_OFFSET, &framenum);
			if (res < 0) {
				p->fr.frametype = AST_FRAME_NULL;
				p->fr.subclass = 0;
				p->fr.offset = AST_FRIENDLY_OFFSET;
				ast_mutex_unlock(&oh323_tab_lock);
				return &(p->fr);
			}
#ifdef	DEBUG_CIRCBUFFER
			ast_log(LOG_DEBUG, "Checked-out %d G.723.1 frames (%d bytes).\n",
							framenum, res);
#endif
			p->fr.samples += 240 * framenum;
			break;

		default:
			/* Do nothing */
#if 0
			ast_log(LOG_NOTICE, "%s: Nothing specific for this format type (%d)?\n",
							c->name, p->capability);
#endif
			break;
	}

	/* H.323 channel read direction open? */
	if ((p->direction != PLAYER) && (p->direction != BOTH)) {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Channel read direction not open (%s).\n", 
								c->name, get_str(dir_str, p->direction));
		p->fr.frametype = AST_FRAME_NULL;
		p->fr.subclass = 0;
		p->fr.offset = AST_FRIENDLY_OFFSET;
		ast_mutex_unlock(&oh323_tab_lock);
		return &(p->fr);
	}

	/* Save the data in a ASTERISK frame struct */
	p->fr.data = p->buf + AST_FRIENDLY_OFFSET;
	p->fr.datalen = res;
	p->fr.frametype = AST_FRAME_VOICE;
	p->fr.subclass = p->capability;
	p->fr.offset = AST_FRIENDLY_OFFSET;
	f = &(p->fr);

	/* Do in-band DTMF detection, if enabled */
	if ((p->capability==AST_FORMAT_ALAW)||(p->capability==AST_FORMAT_ULAW)) {
		if (config_options.userInputMode == UIMODE_INBAND) {
			f = ast_dsp_process(p->owner, p->vad, f);
			if (f->frametype == AST_FRAME_DTMF)
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Got in-band digit %c.\n", 
									c->name, f->subclass);
		}
	}
	ast_mutex_unlock(&oh323_tab_lock);
	return(f);
}

/**
 * Write the data buffer of the frame to the network.
 * Return -1 on error, 0 on success.
 */
static int oh323_write(struct ast_channel *c, struct ast_frame *f)
{
	struct chan_oh323_pvt	*i = (struct chan_oh323_pvt *)c->tech_pvt;
	struct ast_frame		*ftmp;
	int						res, framenum, len;
	char					buf[100];

	ast_mutex_lock(&oh323_tab_lock);

	/* H.323 channel write direction open? */
	if ((i->direction != RECORDER) && (i->direction != BOTH)) {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Channel write direction not open (%s).\n", 
								c->name, get_str(dir_str, i->direction));
		ast_mutex_unlock(&oh323_tab_lock);
		return(0);
	}

	/* If it's already gone, return failure */
	if (c->_state == AST_STATE_DOWN) {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Channel was shut down.\n", c->name);
		ast_mutex_unlock(&oh323_tab_lock);
		return(-1);
	}

	/* Don't send null frames */
	if (f->frametype == AST_FRAME_NULL) {
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Ignoring NULL frame.\n", c->name);
		ast_mutex_unlock(&oh323_tab_lock);
		return(0);
	}

	/* Check for a proper frame type. */
	if (f->frametype != AST_FRAME_VOICE) {
		ast_log(LOG_WARNING, "%s: Don't know what to do with frame type '%d'.\n", 
						c->name, f->frametype);
		ast_mutex_unlock(&oh323_tab_lock);
		return(-1);
	}

	/* Check for format changes */
	if (f->subclass != i->owner->nativeformats) {
		ast_log(LOG_NOTICE, "%s: Format changed to %s (native %s).\n", 
						c->name, 
						ast_getformatname(f->subclass),
						ast_getformatname(c->nativeformats));
		if (ast_set_write_format(c, f->subclass) < 0) {
			ast_mutex_unlock(&oh323_tab_lock);
			return(-1);
		}
		if (ast_set_read_format(c, f->subclass) < 0) {
			ast_mutex_unlock(&oh323_tab_lock);
			return(-1);
		}
		if (i->tx_smooth != NULL) {
			ast_smoother_free(i->tx_smooth);
			i->tx_smooth = NULL;
		}
		ast_mutex_unlock(&oh323_tab_lock);
		return(0);
	}

	/* Create the smoother, if not already there */
	if (i->tx_smooth == NULL) {
		i->tx_smooth = ast_smoother_new(i->tx_buf_size);
		if (i->tx_smooth == NULL) {
			ast_log(LOG_ERROR, "%s: Failed to create smoother.\n", c->name);
			ast_mutex_unlock(&oh323_tab_lock);
			return(-1);
		}
		if (option_debug)
			ast_log(LOG_DEBUG, "%s: Created smoother (size=%d, format=%d).\n", 
						c->name, i->tx_buf_size, f->subclass);
	}

	/* Do specific stuff for each codec type */
	switch (f->subclass) {
		case AST_FORMAT_ULAW:
		case AST_FORMAT_ALAW:
		case AST_FORMAT_G726:
			break;

		case AST_FORMAT_GSM:
			if (f->datalen % 33 != 0) {
				ast_log(LOG_WARNING, "%s: Invalid frame size for GSM (%d bytes).\n", 
						c->name, f->datalen);
				ast_mutex_unlock(&oh323_tab_lock);
				return(0);
			}
			break;

		case AST_FORMAT_G729A:
			if (f->datalen % 10 != 0) {
				ast_log(LOG_WARNING, "%s: Invalid frame size for G.729 (%d bytes).\n", 
						c->name, f->datalen);
				ast_mutex_unlock(&oh323_tab_lock);
				return(0);
			}
			break;

		case AST_FORMAT_G723_1:
			/* Create the smoother, if not already there */
			if (i->tx_g7231_smooth == NULL) {
				i->tx_g7231_smooth = G7231SF_new();
				if (i->tx_g7231_smooth == NULL) {
					ast_log(LOG_ERROR, "%s: Failed to create G.723.1 smoother.\n", c->name);
					ast_mutex_unlock(&oh323_tab_lock);
					return(-1);
				}
				if (option_debug)
					ast_log(LOG_DEBUG, "%s: Created G.723.1 smoother.\n", c->name);
			}
			/* Check-in all the data we received */
			if (G7231SF_push(i->tx_g7231_smooth, f->data, f->datalen) < 0) {
				ast_log(LOG_ERROR, "%s: Failed to fill G.723.1 smoother.\n", c->name);
				ast_mutex_unlock(&oh323_tab_lock);
				return(-1);
			}
#ifdef	DEBUG_CIRCBUFFER
			ast_log(LOG_DEBUG, "Checked-in %d bytes.\n", res);
#endif

			/* Checkout single frames and send them */
			while (!G7231SF_isempty(i->tx_g7231_smooth)) {
				framenum = 1;
				len = G7231SF_pop(i->tx_g7231_smooth, buf, sizeof(buf), &framenum);
				if (framenum != 1) {
					ast_mutex_unlock(&oh323_tab_lock);
					return(0);
				}
				if ((res = write(i->recorder_fd[0], buf, len)) < 0) {
					if (option_debug)
						ast_log(LOG_WARNING, "%s: Unable to write to network (errno=%d).\n", 
									c->name, errno);
					ast_mutex_unlock(&oh323_tab_lock);
					return(0);
				}
				i->txcount += res;
				if (oh323_verbose_debug && (i->txcount % (100 * i->tx_buf_size) < i->tx_buf_size))
					ast_verbose("Channel %s (call '%s') TX byte count is %d.\n", 
							c->name, i->cd.call_token, i->txcount);
			}
			/* Nothing else to do for G.723.1 */
			ast_mutex_unlock(&oh323_tab_lock);
			return(0);
			
		default:
			/* Do nothing */
			ast_log(LOG_NOTICE, "%s: Nothing specific for this format type (%s)?\n",
							c->name, ast_getformatname(f->subclass));
			break;
	}

	ast_smoother_feed(i->tx_smooth, f);
	ftmp = ast_smoother_read(i->tx_smooth);
	while (ftmp != NULL) {
		if ((res = write(i->recorder_fd[0], (char *)(ftmp->data), ftmp->datalen)) < 0) {
			if (option_debug)
				ast_log(LOG_WARNING, "%s: Unable to write to fd %d (%d, %s).\n", 
							c->name, i->recorder_fd[0], errno, strerror(errno));
			//ast_mutex_unlock(&oh323_tab_lock);
			//return(-1);
		}
		i->txcount += res;
		if (oh323_verbose_debug && (i->txcount % (100 * i->tx_buf_size) < i->tx_buf_size))
			ast_verbose("Channel %s (call '%s') TX byte count is %d.\n", 
					c->name, i->cd.call_token, i->txcount);
		ftmp = ast_smoother_read(i->tx_smooth);
	}

	ast_mutex_unlock(&oh323_tab_lock);
	return(0);
}

/**
 * Answer an H.323 channel.
 * Return 0 on success, -1 on error.
 */
static int oh323_answer(struct ast_channel *c)
{
	struct chan_oh323_pvt *pvt = (struct chan_oh323_pvt *)c->tech_pvt;
	struct request_oh323 *e;

	if (option_debug)
		ast_log(LOG_DEBUG, "In oh323_answer (%s).\n", c->name);

	ast_mutex_lock(&oh323_tab_lock);
	if (pvt->i_state == OH323_STATE_ESTABLISHED) {
		ast_log(LOG_DEBUG, "%s: Channel is ESTABLISHED???\n", c->name);
		ast_mutex_unlock(&oh323_tab_lock);
		return(0);
	}
	if (c->_state == AST_STATE_UP) {
		ast_log(LOG_DEBUG, "%s: Channel is UP???\n", c->name);
		ast_mutex_unlock(&oh323_tab_lock);
		return(0);
	}

	MYMARK("Answering call");
	OBJ_ALLOC(struct request_oh323, e);
	e->type = REQ_ANSWER;
	strncpy((e->answer).token, (pvt->cd).call_token, sizeof((e->answer).token)-1);
	OBJLIST_INSERT(&requestl, e);
	ast_sched_add(oh323_sched, DEFAULT_REQTIME, oh323_exec_request, NULL);

	/* The call was answered successfully */
	if (option_debug)
		ast_log(LOG_DEBUG, "%s: Call answered.\n", c->name);
	ast_setstate(c, AST_STATE_UP);
	ast_mutex_unlock(&oh323_tab_lock);
	return(0);
}

static int oh323_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
{
	struct chan_oh323_pvt *p = (struct chan_oh323_pvt *)newchan->tech_pvt;

	ast_mutex_lock(&oh323_tab_lock);
	if (p->owner != oldchan) {
		ast_log(LOG_WARNING, "Old channel wasn't %p but was %p\n", oldchan, p->owner);
		ast_mutex_unlock(&oh323_tab_lock);
		return(-1);
	}
	p->owner = newchan;
	ast_mutex_unlock(&oh323_tab_lock);
	return(0);
}

/**
 * Allocate and initialize a "chan_oh323_pvt" private structure.
 * Return NULL on error, the pointer to the struct on success.
 */
static struct chan_oh323_pvt *new_oh323(int index)
{
	struct chan_oh323_pvt *tmp;
	int flags, i;

	tmp = malloc(sizeof(struct chan_oh323_pvt));
	if (tmp == NULL) {
		ast_log(LOG_ERROR, "Out of memory!\n");
		return(NULL);
	}
	memset(tmp, 0, sizeof(struct chan_oh323_pvt));

	/* Create socketpairs */
	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, tmp->player_fd) != 0) {
		ast_log(LOG_ERROR, "Failed to create socketpair for player(%d, %s).\n",
						errno, strerror(errno));
		free(tmp);
		return NULL;
	}
	if (shutdown(tmp->player_fd[1], SHUT_RD) != 0) {
		ast_log(LOG_ERROR, "Failed to configure player socket.\n");
		free(tmp);
		return NULL;
	}
	if (socketpair(AF_LOCAL, SOCK_STREAM, 0, tmp->recorder_fd) != 0) {
		ast_log(LOG_ERROR, "Failed to create socketpair for recorder(%d, %s).\n",
						errno, strerror(errno));
		free(tmp);
		return NULL;
	}
	if (shutdown(tmp->recorder_fd[1], SHUT_WR) != 0) {
		ast_log(LOG_ERROR, "Failed to configure recorder socket.\n");
		free(tmp);
		return NULL;
	}
	/* Set all fds in non-blocking mode */
	for (i=0; i<2; i++) {
		flags = fcntl(tmp->player_fd[i], F_GETFL);
		if (fcntl(tmp->player_fd[i], F_SETFL, flags | O_NONBLOCK) < 0) {
			ast_log(LOG_ERROR, "Failed to configure player socket.\n");
			free(tmp);
			return NULL;
		}
	}
	for (i=0; i<2; i++) {
		flags = fcntl(tmp->recorder_fd[i], F_GETFL);
		if (fcntl(tmp->recorder_fd[i], F_SETFL, flags | O_NONBLOCK) < 0) {
			ast_log(LOG_ERROR, "Failed to configure recorder socket.\n");
			free(tmp);
			return NULL;
		}
	}
	/* Create the event pipe */
	if (pipe(tmp->event_pipe) < 0) {
		ast_log(LOG_ERROR, "Failed to create event pipe (%d, %s).\n", errno, strerror(errno));
		free(tmp);
		return NULL;
	} else {
		flags = fcntl(tmp->event_pipe[0], F_GETFL);
		fcntl(tmp->event_pipe[0], F_SETFL, flags | O_NONBLOCK);
		flags = fcntl(tmp->event_pipe[1], F_GETFL);
		fcntl(tmp->event_pipe[1], F_SETFL, flags | O_NONBLOCK);
	}

	if (option_debug)
		ast_log(LOG_DEBUG, "Player fds %d,%d - Recorder fds %d,%d - Event pipe %d,%d.\n",
					tmp->player_fd[0], tmp->player_fd[1], 
					tmp->recorder_fd[0], tmp->recorder_fd[1],
					tmp->event_pipe[0], tmp->event_pipe[1]);

	tmp->direction = NONE;
	tmp->alreadygone = 0;
	tmp->sched_id = -1;
	tmp->close_recorder_fd = 1;
	tmp->close_player_fd = 1;
	tmp->normread = 0;
	tmp->rxcount = 0;
	tmp->txcount = 0;
	tmp->from_remote = 0;
	tmp->clearing_call = 0;
	tmp->tx_smooth = NULL;
	tmp->i_state = OH323_STATE_NULL;
	tmp->needs_destroy = 0;
	tmp->progress = 0;
	OBJLIST_INIT(&(tmp->exceptl));
	tmp->head_stats = tmp->rtp_stats;
	tmp->tail_stats = tmp->rtp_stats;
	return(tmp);
}


static void oh323_close_call_fds(int index)
{
	struct chan_oh323_pvt *pvt = oh323_tab[index];

	/* Close open sockets */
	if (option_debug)
		ast_log(LOG_DEBUG, "Player fds %d,%d - Recorder fds %d,%d.\n",
					pvt->player_fd[0], pvt->player_fd[1], 
					pvt->recorder_fd[0], pvt->recorder_fd[1]);
	/* Handle special cases where only the one logical channel of
	 * the connection has been opened. */
	if (pvt->player_fd[0] > -1)
		close(pvt->player_fd[0]);
	pvt->player_fd[0] = -1;
	if (pvt->close_player_fd) {
		if (option_debug)
			ast_log(LOG_DEBUG, "Closing socket %d.\n", pvt->player_fd[1]);
		if (pvt->player_fd[1] > -1)
			close(pvt->player_fd[1]);
		pvt->player_fd[1] = -1;
		pvt->close_player_fd = 0;
	}
	if (pvt->recorder_fd[0] > -1)
		close(pvt->recorder_fd[0]);
	pvt->recorder_fd[0] = -1;
	if (pvt->close_recorder_fd) {
		if (option_debug)
			ast_log(LOG_DEBUG, "Closing socket %d.\n", pvt->recorder_fd[1]);
		if (pvt->recorder_fd[1] > -1)
			close(pvt->recorder_fd[1]);
		pvt->recorder_fd[1] = -1;
		pvt->close_recorder_fd = 0;
	}
}

/**
 * Pick a codec.
 */
static int oh323_codec_choose(int formats)
{
	struct oh323_codecinfo *ci = config_options.codecInfo;
	return(ci->format);
}

/**
 * Destroy allocated resources of an existing "chan_oh323_pvt"
 * private structure. Does not free the allocated memory. The caller
 * should do it.
 * Returns nothing.
 */
static void oh323_destroy(int index)
{
	struct chan_oh323_pvt *pvt = oh323_tab[index];

	if (option_debug)
		ast_log(LOG_DEBUG, "Releasing allocated resources (%d).\n", index);

	clear_call_details(&(pvt->cd));
	if (option_debug)
		ast_log(LOG_DEBUG, "Event pipe %d,%d.\n", pvt->event_pipe[0], pvt->event_pipe[1]);
	close(pvt->event_pipe[0]);
	close(pvt->event_pipe[1]);
	if ((config_options.userInputMode == UIMODE_INBAND)&&(pvt->vad != NULL))
		ast_dsp_free(pvt->vad);
	ast_smoother_free(pvt->tx_smooth);
	G7231SF_free(pvt->rx_g7231_smooth);
	pvt->rx_g7231_smooth = NULL;
	G7231SF_free(pvt->tx_g7231_smooth);
	pvt->tx_g7231_smooth = NULL;
	pvt->i_state = OH323_STATE_NULL;
	pvt->direction = NONE;
	pvt->alreadygone = 1;
	pvt->owner = NULL;
}

/**
 * Allocate and initialize a "ast_channel" (ASTERISK channel)
 * structure. Associate the provided H.323 private structure
 * with the new channel.
 * Return NULL on error, the pointer to the struct on success.
 */
static struct ast_channel *ast_oh323_new(struct chan_oh323_pvt *i, int state, char *chan_name)
{
	struct ast_channel *tmp;
	int dnamelen;
	char *context;
	int fmt;

	if (i == NULL) {
		ast_log(LOG_ERROR, "Private structure is NULL!\n");
		return NULL;
	}
	if (chan_name == NULL) {
		ast_log(LOG_ERROR, "Channel name is NULL!\n");
		return NULL;
	}

	tmp = ast_channel_alloc(1, state, "", "", config_options.accountCode, "", "", config_options.amaFlags, NULL);
	if (tmp == NULL) {
		ast_log(LOG_WARNING, "Failed to allocate an Asterisk channel.\n");
		return NULL;
	}
	tmp->tech = &oh323_tech;
	tmp->tech_pvt = i;

	ast_string_field_build(tmp, name, "OH323/%s-%0x", chan_name, i->ep_settings.ud.app_id);

	/* Set channel language and musicclass */
	if (strlen(config_options.language))
		ast_string_field_set(tmp, language, config_options.language);
	if (strlen(config_options.musicclass))
		ast_string_field_set(tmp, musicclass, config_options.musicclass);

	if (config_options.userInputMode == UIMODE_INBAND) {
		i->vad = ast_dsp_new();
		ast_dsp_set_features(i->vad, DSP_FEATURE_DTMF_DETECT);
		ast_dsp_digitmode(i->vad, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
	}
	tmp->fds[0] = i->event_pipe[0];
	tmp->fds[1] = i->player_fd[0];

	/* Set our native format */
	/* XXX: Only one should be selected */
	tmp->nativeformats = oh323_capability;
	fmt = oh323_codec_choose(tmp->nativeformats);
	/*fmt = ast_best_codec(tmp->nativeformats);*/

	tmp->writeformat = fmt;
	tmp->rawwriteformat = fmt;
	tmp->readformat = fmt;
	tmp->rawreadformat = fmt;

	if (option_debug)
		ast_log(LOG_DEBUG, "%s: Raw format set to %s.\n", tmp->name, ast_getformatname(fmt));

	i->owner = tmp;

	/* Set initial context and extension */
	/* First, search for a matching alias, then for a matching prefix */
	/* or return the default context. */
	/* The extension is the same as the called E.164 address of the call, */
	/* or the default extension, if this cannot be determined */
	strncpy(tmp->context, config_options.context, sizeof(tmp->context)-1);
	strncpy(tmp->exten, "s", sizeof(tmp->exten)-1);
	tmp->priority = 1;
	if (i->from_remote) {
		if (i->cd.call_dest_e164 != NULL) {
			if (strlen(i->cd.call_dest_e164) > 0) {
				if (!context_from_alias((char*)i->cd.call_dest_e164, &context))
					strncpy(tmp->context, context, sizeof(tmp->context)-1);
				else if (!context_from_prefix((char*)i->cd.call_dest_e164, &context))
					strncpy(tmp->context, context, sizeof(tmp->context)-1);
				else
					strncpy(tmp->context, config_options.context, sizeof(tmp->context)-1);
				strncpy(tmp->exten, i->cd.call_dest_e164, sizeof(tmp->exten)-1);
			} 
		}
	}
	if (strlen(tmp->exten) && strcmp(tmp->exten, "s"))
		tmp->cid.cid_dnid = strdup(tmp->exten);

	/* Set caller ID */
	if ((i->cd.call_source_alias != NULL) && (strlen(i->cd.call_source_alias) > 0)) { 
		dnamelen = strcspn(i->cd.call_source_alias, "([");
		if (dnamelen < strlen(i->cd.call_source_alias)) {
			tmp->cid.cid_name = (char *)malloc(dnamelen + 10);
			if (tmp->cid.cid_name == NULL) {
				ast_log(LOG_WARNING, "Failed to allocate memory.\n");
				return NULL;
			}
			memset(tmp->cid.cid_name, 0, dnamelen + 10);
			strncat(tmp->cid.cid_name, i->cd.call_source_alias, dnamelen);
		} else {
			tmp->cid.cid_name = NULL;
		}
	}
	if ((i->cd.call_source_e164 != NULL) && (strlen(i->cd.call_source_e164) > 0)) {
		dnamelen = strlen(i->cd.call_source_e164);
		tmp->cid.cid_num = (char *)malloc(dnamelen + 10);
		if (tmp->cid.cid_num == NULL) {
			ast_log(LOG_WARNING, "Failed to allocate memory.\n");
			return NULL;
		}
		memset(tmp->cid.cid_num, 0, dnamelen + 10);
		strncat(tmp->cid.cid_num, i->cd.call_source_e164, dnamelen);
	} else {
		tmp->cid.cid_num = NULL;
	}

	/* Set ANI */
	if (tmp->cid.cid_num != NULL)
		tmp->cid.cid_ani = strdup(tmp->cid.cid_num);

	if (option_debug) {
		ast_log(LOG_DEBUG, "Context is '%s', extension is '%s'.\n", tmp->context, tmp->exten);
		ast_log(LOG_DEBUG, "CID name '%s', CID num '%s', ANI '%s', DNID '%s'.\n", 
						tmp->cid.cid_name ? tmp->cid.cid_name:"UNKNOWN",
						tmp->cid.cid_num ? tmp->cid.cid_num:"UNKNOWN",
						tmp->cid.cid_ani ? tmp->cid.cid_ani:"UNKNOWN",
						tmp->cid.cid_dnid ? tmp->cid.cid_dnid:"UNKNOWN");
	}

	/* Set environment variables */
	pbx_builtin_setvar_helper(tmp, "OH323_CALLID", (char*)((i->cd).call_id));
	pbx_builtin_setvar_helper(tmp, "OH323_CONFID", (char*)((i->cd).conf_id));
	pbx_builtin_setvar_helper(tmp, "OH323_CTOKEN", (char*)((i->cd).call_token));
	pbx_builtin_setvar_helper(tmp, "OH323_SRCALIAS", (char*)((i->cd).call_source_alias));
	pbx_builtin_setvar_helper(tmp, "OH323_DSTALIAS", (char*)((i->cd).call_dest_alias));
	pbx_builtin_setvar_helper(tmp, "OH323_SRCE164", (char*)((i->cd).call_source_e164));
	pbx_builtin_setvar_helper(tmp, "OH323_DSTE164", (char*)((i->cd).call_dest_e164));
	pbx_builtin_setvar_helper(tmp, "OH323_REMOTEAPP", (char*)((i->cd).remote_app));
	pbx_builtin_setvar_helper(tmp, "OH323_RADDR", (char*)((i->cd).remote_addr));
	pbx_builtin_setvar_helper(tmp, "OH323_LADDR", (char*)((i->cd).local_addr));

	/* Update use count */
	ast_mutex_lock(&usecnt_lock);
	usecnt++;
	ast_mutex_unlock(&usecnt_lock);
	ast_update_use_count();

	ast_setstate(tmp, state);
	if (state != AST_STATE_DOWN) {
		if (ast_pbx_start(tmp)) {
			ast_log(LOG_WARNING, "Unable to start PBX on %s.\n", tmp->name);
			ast_hangup(tmp);
			tmp = NULL;
		}
	}
	return tmp;
}

/**
 * Create an ASTERISK H.323 channel with the requested format.
 * This function limits the number of outbound H.323 calls.
 * Return NULL on error, the pointer to the channel on success.
 */
static struct ast_channel *oh323_request(const char *type, int format, void *data, int *cause)
{
	int i=0, count, simcount, oldformat;
	struct ast_channel *c;
	char *dest = data;

	if (option_debug)
		ast_log(LOG_DEBUG, "In oh323_request: type=%s, format=%d, data=%s.\n", 
						type, format, (char *)data);

	/* Check the format requested */
	oldformat = format;
	format &= oh323_full_capability;
	if (!format) {
		ast_log(LOG_ERROR, "Asked to get a channel of unsupported format '%d'\n", format);
		return NULL;
	}

	ast_mutex_lock(&oh323_tab_lock);

	/* Check the number of active outbound H.323 calls */
	count = 0;
	simcount = 0;
	for (i=0; i<config_options.totalNum; i++)
		if (oh323_tab[i] != NULL) {
			if (oh323_tab[i]->from_remote == 0)
				++count;
			++simcount;
		}
	if ((count >= config_options.outboundMax) ||
		((config_options.simultaneousMax > 0) && 
		 (simcount >= config_options.simultaneousMax))) {
		ast_log(LOG_WARNING, "Blocking outbound H.323 call due to call-limit violation.\n");

		/* Update statistics */
		ast_mutex_lock(&oh323_stats_lock);
		++oh323_stats.block_outcall;
		ast_mutex_unlock(&oh323_stats_lock);

		ast_mutex_unlock(&oh323_tab_lock);
		return NULL;
	}

	/* Search for a free entry in the private structure table. */
	for (i=0; i<config_options.totalNum; i++)
		if (oh323_tab[i] == NULL)
			break;

	/* No free entry */
	if ((i<0) || (i>=config_options.totalNum)) {
		ast_log(LOG_ERROR, "Unable to accept more calls.\n");
		ast_mutex_unlock(&oh323_tab_lock);
		return NULL;
	}

	oh323_tab[i] = new_oh323(i);
	if (oh323_tab[i] == NULL) {
		ast_log(LOG_WARNING, "Failed to create new OH323 private structure %d.\n", i);
		ast_mutex_unlock(&oh323_tab_lock);
		return NULL;
 	}

	if (option_debug)
		ast_log(LOG_DEBUG, "Created new call structure %d (%d bytes).\n", 
						i, sizeof(*(oh323_tab[i])));
	oh323_tab[i]->ep_settings.ud.app_id = generate_uid();
	c = ast_oh323_new(oh323_tab[i], AST_STATE_DOWN, dest);
	if (c == NULL) {
		ast_log(LOG_WARNING, "Failed to create new OH323 Asterisk channel %d.\n", i);
		ast_mutex_unlock(&oh323_tab_lock);
		return NULL;
	}

	oh323_tab[i]->owner = c;

	/* Specify our native formats */
	c->nativeformats = format;
	c->rawwriteformat = format;
	c->rawreadformat = format;
	if (option_debug)
		ast_log(LOG_DEBUG, "%s: Native format changed to %s.\n", 
									c->name, ast_getformatname(format));

	ast_mutex_unlock(&oh323_tab_lock);
	return c;
}

/**
 * Copy the call_details structure to a local structure.
 * All the "const char *" variables are malloc'ed with this function.
 * The right cleanup should be done by calling the "clear_call_details".
 * Return -1 on error, 0 on success.
 */
static int copy_call_details(call_details_t *src_cd, call_details_t *dest_cd)
{
	*dest_cd = *src_cd;
	if (option_debug) {
		ast_log(LOG_DEBUG, "--- CALL DETAILS ---\n");
		ast_log(LOG_DEBUG, "call_id = '%s'\n", dest_cd->call_id);
		ast_log(LOG_DEBUG, "conf_id = '%s'\n", dest_cd->conf_id);
		ast_log(LOG_DEBUG, "call_token = '%s'\n", dest_cd->call_token);
		ast_log(LOG_DEBUG, "call_source_alias = '%s'\n", dest_cd->call_source_alias);
		ast_log(LOG_DEBUG, "call_dest_alias = '%s'\n", dest_cd->call_dest_alias);
		ast_log(LOG_DEBUG, "call_source_e164 = '%s'\n", dest_cd->call_source_e164);
		ast_log(LOG_DEBUG, "call_dest_e164 = '%s'\n", dest_cd->call_dest_e164);
		ast_log(LOG_DEBUG, "call_rdnis = '%s'\n", dest_cd->call_rdnis);
		ast_log(LOG_DEBUG, "remote_app = '%s'\n", dest_cd->remote_app);
		ast_log(LOG_DEBUG, "remote_addr = '%s'\n", dest_cd->remote_addr);
		ast_log(LOG_DEBUG, "local_addr = '%s'\n", dest_cd->local_addr);
	}
	
	return(0);
}

/**
 * Cleanup all the variables of the call_details structure.
 * Returns nothing.
 */
static void clear_call_details(call_details_t *cd)
{
	if (!cd) {
		ast_log(LOG_WARNING, "Call details struct is NULL!\n");
		CRASH;
	}
	memset((char*)cd, 0, sizeof(call_details_t));
}

/**
 * Determine the ASTERISK format equivalent to the specified codec.
 * Returns non-zero in success, 0 in case of an unsupported codec.
 */
static int oh323_codec2format(int codec)
{
	switch (codec) {
		case G711U:
			/*return(AST_FORMAT_SLINEAR);*/
			return(AST_FORMAT_ULAW);
		case G711A:
			/*return(AST_FORMAT_SLINEAR);*/
			return(AST_FORMAT_ALAW);
		case G7231: /* fall-through */
		case G72316K3: /* fall-through */
		case G72315K3: /* fall-through */
		case G7231A6K3: /* fall-through */
		case G7231A5K3:
			return(AST_FORMAT_G723_1);
		case GSM0610:
			return(AST_FORMAT_GSM);
		case LPC10:
			return(AST_FORMAT_SLINEAR);
		case G729:
		case G729A:
		case G729B:
		case G729AB:
			return(AST_FORMAT_G729A);
		case SPEEXN8K:
			return(AST_FORMAT_SPEEX);
		case G726:
		case G72616K:
		case G72624K:
		case G72632K:
		case G72640K: /* XXX This is very bad */
			return(AST_FORMAT_G726);
		case MSGSM: /* fall-through */
		default:
			return(0);
	}
}

static char *oh323_codecset2str(int show_pref, struct oh323_codecinfo *ci, char **res)
{
	char buf[20];
	int  i;

	*res = (char *)malloc(1024);
	if (*res == NULL)
		return(NULL);
	memset(*res, 0, 1024);
	i = 0;
	while (ci) {
		strcat(*res, ast_getformatname(ci->format));
		memset(buf, 0, sizeof(buf));
		if (show_pref)
			sprintf(buf, "<%d> ", i);
		else
			sprintf(buf, " ");
		strcat(*res, buf);
		ci = ci->next;
		i++;
	}
	return(*res);
}

static void oh323_format2codecset(int formats, int *set, int set_size)
{
	int	i, count;
	struct oh323_codecinfo *p, *ci = config_options.codecInfo;

	if (set_size < 1)
		return;

	count = 0;
	p = ci;
	while (p) {
		if (p->format & formats) {
			if (count < set_size) {
				set[count] = p->codec;
				count++;
			}
		}
		p = p->next;
	}
	/* Rest entries are unused */
	for (i = count; i < set_size; i++)
		set[i] = 0;

	if (option_debug)
		for (i = 0; i < set_size; i++)
			ast_log(LOG_DEBUG, "capability_set[%d] - %d\n", i, set[i]);
}

/**
 * Find under which context the specified "alias" resides.
 * Return the pointer to the context in "context" and 0 on success,
 * -1 on error or no match.
 */
static int context_from_alias(char *alias, char **context)
{
	struct oh323_reginfo *regtmp;
	int i;

	if ((alias == NULL)||(context == NULL)||(strlen(alias) <= 0))
		return(-1);

	regtmp = config_options.regInfo;
	*context = NULL;
	while (regtmp) {
		for (i=0; i<regtmp->alias_num; i++)
			if (!strcasecmp(regtmp->alias[i], alias)) {
				*context = regtmp->context;
				if (option_debug)
					ast_log(LOG_DEBUG, "Routing alias '%s' in context '%s'.\n",
									alias, *context);
				return(0);
			}
		regtmp = regtmp->next;
	}
	return(-1);
}

/**
 * Find under which context the specified "prefix" resides.
 * Return the pointer to the context in "context" and 0 on success,
 * -1 on error or no match.
 */
static int context_from_prefix(char *prefix, char **context)
{
	struct oh323_reginfo *regtmp;
	int i;

	if ((prefix == NULL)||(context == NULL)||(strlen(prefix) <= 0))
		return(-1);

	regtmp = config_options.regInfo;
	*context = NULL;
	while (regtmp) {
		for (i=0; i<regtmp->prefix_num; i++)
			if (!strncasecmp(regtmp->prefix[i], prefix, strlen(regtmp->prefix[i]))) {
				*context = regtmp->context;
				if (option_debug)
					ast_log(LOG_DEBUG, "Routing alias '%s' in context '%s'.\n",
									prefix, *context);
				return(0);
			}
		regtmp = regtmp->next;
	}
	return(-1);
}

/**
 * Search the already allocated private structures for a matching call 
 * token or application ID.
 * It returns the index of the call with that token or -1 if not found.
 *
 * NOTE: The caller must lock the list of H.323 calls before calling
 * this function.
 */
static int find_call(const char *token, unsigned int id)
{
	int i;

	if (token && !ast_strlen_zero(token)) {
		for (i = 0; i < config_options.totalNum; i++)
			if (oh323_tab[i] != NULL) {
				if (oh323_tab[i]->cd.call_token) {
					if (!strcmp(oh323_tab[i]->cd.call_token, token))
						return(i);
				} else {
					ast_log(LOG_WARNING, "The call token in %d is NULL!!!\n", i);
				}
			}
	}
	/* Token is NULL, search with application ID */
	for (i = 0; i < config_options.totalNum; i++)
		if (oh323_tab[i] != NULL) {
			if ((oh323_tab[i]->ep_settings).ud.app_id == id)
				return(i);
		}
	return(-1);
}

static int update_call_ids(struct chan_oh323_pvt *p, call_details_t cd)
{
	return(0);
}

/******************************************************************************/
/* Wrapper library's callbacks ************************************************/

/* Call-back function prototypes */
char *setup_h323_connection(call_details_t cd, lchan_dir_t dir, int bs, int codec, int *fd);
char *cleanup_h323_connection(call_details_t cd);
int alerted_h323_connection(call_details_t cd);
int init_h323_connection(call_details_t cd);
int get_h323_userdata(user_details_t *);
int exception_h323_connection(call_details_t cd, int type, char *data);
int stats_h323_connection(call_details_t cd, rtp_stats_t rs);

/**
 * Call-back function for establishing communication (media channels)
 * between Asterisk PBX and OpenH323 library.
 * Returns NULL on error.
 */
char *setup_h323_connection(call_details_t cd, lchan_dir_t dir, int bs, int codec, int *fd)
{
	int	i;
	char fdupdate_buf[2] = { OH323_EVENT_FDUPDATE };
	int needsfdupdate = 0;

	MARK_IN();

	if (option_debug)
		ast_log(LOG_DEBUG, "Setting up call '%s-%08x'.\n", cd.call_token, cd.app_id);

	ast_mutex_lock(&oh323_tab_lock);

	switch (dir) {
	case RECORDER:
		if (option_debug)
			ast_log(LOG_DEBUG, "Call '%s-%08x', direction RECORDER.\n",
								cd.call_token, cd.app_id);
		break;
	case PLAYER:
		if (option_debug)
			ast_log(LOG_DEBUG, "Call '%s-%08x', direction PLAYER.\n",
								cd.call_token, cd.app_id);
		break;
	default:
		ast_log(LOG_NOTICE, "Call '%s-%08x', unknown direction %d.\n", 
								cd.call_token, cd.app_id, dir);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return NULL;
	}

	*fd = -1;

	i = find_call(cd.call_token, cd.app_id);
	if (i < 0) {
		ast_log(LOG_WARNING, "Call '%s-%08x' not found (setup).\n", 
							cd.call_token, cd.app_id);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return NULL;
	}

	/* Call reference match, the struct is already there. */
	/* Check the direction requested. */
	if (oh323_tab[i]->direction == BOTH) {
		ast_log(LOG_WARNING, "Call '%s-%08x' requests connection re-setup (%d).\n", 
						cd.call_token, cd.app_id, dir);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return NULL;
#if 0
		/* XXX Re-setup the direction again ... */
		switch (dir) {
		case PLAYER:
			if (oh323_tab[i]->player_fd[0] > -1)
				close(oh323_tab[i]->player_fd[0]);
			oh323_tab[i]->player_fd[0] = -1;
			/* Open a new player_fd pair */
			if (socketpair(AF_LOCAL, SOCK_STREAM, 0, oh323_tab[i]->player_fd) != 0) {
				ast_log(LOG_ERROR, "Failed to create socketpair for player(%d, %s).\n",
								errno, strerror(errno));
				ast_mutex_unlock(&oh323_tab_lock);
				MARK_OUT();
				return NULL;
			}
			if (shutdown(oh323_tab[i]->player_fd[1], SHUT_RD) != 0) {
				ast_log(LOG_ERROR, "Failed to configure player socket.\n");
				ast_mutex_unlock(&oh323_tab_lock);
				MARK_OUT();
				return NULL;
			}
			/* Set fds in non-blocking mode */
			for (j=0; j<2; j++) {
				flags = fcntl(oh323_tab[i]->player_fd[j], F_GETFL);
				if (fcntl(oh323_tab[i]->player_fd[j], F_SETFL, flags | O_NONBLOCK) < 0) {
					ast_log(LOG_ERROR, "Failed to configure player socket.\n");
					ast_mutex_unlock(&oh323_tab_lock);
					MARK_OUT();
					return NULL;
				}
			}
			if (option_debug)
				ast_log(LOG_DEBUG, "Call '%s' re-opened player fds %d, %d.\n", 
								cd.call_token, 
								oh323_tab[i]->player_fd[0],
								oh323_tab[i]->player_fd[1]);
			break;
		case RECORDER:
			if (oh323_tab[i]->recorder_fd[0] > -1)
				close(oh323_tab[i]->recorder_fd[0]);
			oh323_tab[i]->recorder_fd[0] = -1;
			/* Open a new player_fd pair */
			if (socketpair(AF_LOCAL, SOCK_STREAM, 0, oh323_tab[i]->recorder_fd) != 0) {
				ast_log(LOG_ERROR, "Failed to create socketpair for recorder(%d, %s).\n",
								errno, strerror(errno));
				ast_mutex_unlock(&oh323_tab_lock);
				MARK_OUT();
				return NULL;
			}
			if (shutdown(oh323_tab[i]->recorder_fd[1], SHUT_WR) != 0) {
				ast_log(LOG_ERROR, "Failed to configure recorder socket.\n");
				ast_mutex_unlock(&oh323_tab_lock);
				MARK_OUT();
				return NULL;
			}
			/* Set fds in non-blocking mode */
			for (j=0; j<2; j++) {
				flags = fcntl(oh323_tab[i]->recorder_fd[j], F_GETFL);
				if (fcntl(oh323_tab[i]->recorder_fd[j], F_SETFL, flags | O_NONBLOCK) < 0) {
					ast_log(LOG_ERROR, "Failed to configure recorder socket.\n");
					ast_mutex_unlock(&oh323_tab_lock);
					MARK_OUT();
					return NULL;
				}
			}
			if (option_debug)
				ast_log(LOG_DEBUG, "Call '%s' re-opened recorder fds %d, %d.\n", 
								cd.call_token, 
								oh323_tab[i]->recorder_fd[0],
								oh323_tab[i]->recorder_fd[1]);
 			break;
		default:
			CRASH;
		}
		needsfdupdate = 1;
#endif
	}

	if (oh323_tab[i]->direction == dir) {
		ast_log(LOG_WARNING, "Call '%s-%08x' invalid direction request (%d).\n", 
						cd.call_token, cd.app_id, dir);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return NULL;
	}
	if (oh323_tab[i]->direction == NONE) {
		if (option_debug)
			ast_log(LOG_DEBUG, "Call '%s-%08x' requests direction %d.\n", 
							cd.call_token, cd.app_id, dir);
		if (copy_call_details(&cd, &(oh323_tab[i]->cd)) != 0){
			ast_log(LOG_ERROR, "Failed to copy call details.\n");
			ast_mutex_unlock(&oh323_tab_lock);
			MARK_OUT();
			return NULL;
		}
		oh323_tab[i]->direction = dir;
		if (dir == PLAYER) {
			oh323_tab[i]->rx_buf_size = bs;
			*fd = oh323_tab[i]->player_fd[1];
			if (option_debug)
				ast_log(LOG_DEBUG, "Call '%s-%08x' player fd %d sent.\n", 
								cd.call_token, cd.app_id, oh323_tab[i]->player_fd[1]);
			oh323_tab[i]->close_player_fd = 0; /* Don't close this fd */
		} else if (dir == RECORDER) {
			oh323_tab[i]->tx_buf_size = bs;
			*fd = oh323_tab[i]->recorder_fd[1];
			if (option_debug)
				ast_log(LOG_DEBUG, "Call '%s-%08x' recorder fd %d sent.\n", 
								cd.call_token, cd.app_id, oh323_tab[i]->recorder_fd[1]);
			oh323_tab[i]->close_recorder_fd = 0; /* Don't close this fd */
		}

		/* Set the capability of the channel */
		oh323_tab[i]->capability = oh323_codec2format(codec);
		oh323_tab[i]->last_tx_format = oh323_tab[i]->capability;
		oh323_tab[i]->last_rx_format = oh323_tab[i]->capability;
		if (oh323_tab[i]->capability == 0) {
			ast_log(LOG_NOTICE, "Call '%s-%08x' requested unsupported codec %d!\n", 
							cd.call_token, cd.app_id, codec);
			ast_mutex_unlock(&oh323_tab_lock);
			MARK_OUT();
			return NULL;
		}
		if (oh323_tab[i]->owner != NULL) {
			if (option_debug)
				ast_log(LOG_DEBUG, "Setting channel '%s' native format to %s!\n", 
								oh323_tab[i]->owner->name,
								ast_getformatname(oh323_tab[i]->capability));
			oh323_tab[i]->owner->nativeformats = oh323_tab[i]->capability;
		}

		/* Set environment variables */
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_CALLID", 
					(char*)((oh323_tab[i]->cd).call_id));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_CONFID", 
					(char*)((oh323_tab[i]->cd).conf_id));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_CHANCODEC", 
					ast_getformatname(oh323_codec2format(codec)));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_CTOKEN", 
					(char*)((oh323_tab[i]->cd).call_token));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_SRCALIAS", 
					(char*)((oh323_tab[i]->cd).call_source_alias));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_DSTALIAS", 
					(char*)((oh323_tab[i]->cd).call_dest_alias));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_SRCE164", 
					(char*)((oh323_tab[i]->cd).call_source_e164));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_DSTE164", 
					(char*)((oh323_tab[i]->cd).call_dest_e164));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_REMOTEAPP", 
					(char*)((oh323_tab[i]->cd).remote_app));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_RADDR", 
					(char*)((oh323_tab[i]->cd).remote_addr));
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_LADDR", 
					(char*)((oh323_tab[i]->cd).local_addr));

		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return "asterisk-oh323";
	}

	if (option_debug)
		ast_log(LOG_DEBUG, "Call '%s-%08x' found in entry %d (setup).\n", 
						cd.call_token, cd.app_id, i);
	oh323_tab[i]->direction = BOTH;
	if (dir == PLAYER) {
		oh323_tab[i]->rx_buf_size = bs;
		*fd = oh323_tab[i]->player_fd[1];
		if (option_debug)
			ast_log(LOG_DEBUG, "Call '%s-%08x' player fd %d sent.\n", 
							cd.call_token, cd.app_id, oh323_tab[i]->player_fd[1]);
		oh323_tab[i]->close_player_fd = 0; /* Don't close this fd */
	} else if (dir == RECORDER) {
		oh323_tab[i]->tx_buf_size = bs;
		*fd = oh323_tab[i]->recorder_fd[1];
		if (option_debug)
			ast_log(LOG_DEBUG, "Call '%s-%08x' recorder fd %d sent.\n", 
							cd.call_token, cd.app_id, oh323_tab[i]->recorder_fd[1]);
		oh323_tab[i]->close_recorder_fd = 0; /* Don't close this fd */
	}
	/* Set (again) the capability of the channel */
	if (oh323_tab[i]->capability != oh323_codec2format(codec)) {
		ast_log(LOG_WARNING, "Call '%s-%08x' format changed from %s to %s!\n", 
						cd.call_token,
						cd.app_id,
						ast_getformatname(oh323_tab[i]->capability), 
						ast_getformatname(oh323_codec2format(codec)));
		oh323_tab[i]->capability = oh323_codec2format(codec);
		oh323_tab[i]->last_tx_format = oh323_tab[i]->capability;
		oh323_tab[i]->last_rx_format = oh323_tab[i]->capability;
		if (oh323_tab[i]->capability == 0) {
			ast_log(LOG_DEBUG, "Call '%s-%08x' requested unsupported codec %d!\n", 
							cd.call_token, cd.app_id, codec);
			ast_mutex_unlock(&oh323_tab_lock);
			MARK_OUT();
			return NULL;
		}
		if (oh323_tab[i]->owner != NULL) {
			if (option_debug)
				ast_log(LOG_DEBUG, "Setting channel '%s' native format to %s!\n", 
								oh323_tab[i]->owner->name,
								ast_getformatname(oh323_tab[i]->capability));
			oh323_tab[i]->owner->nativeformats = oh323_tab[i]->capability;
			if (oh323_tab[i]->tx_smooth) {
				ast_smoother_free(oh323_tab[i]->tx_smooth);
				oh323_tab[i]->tx_smooth = NULL;
			}
		}
		pbx_builtin_setvar_helper(oh323_tab[i]->owner, "OH323_CHANCODEC", 
					ast_getformatname(oh323_codec2format(codec)));
	}
	if (needsfdupdate) {
		if (write(oh323_tab[i]->event_pipe[1], fdupdate_buf, 1) != 1) {
			ast_log(LOG_WARNING, "Failed to write to event pipe of call '%s-%08x'.\n",
							cd.call_token, cd.app_id);
		}
	}

	ast_mutex_unlock(&oh323_tab_lock);
	MARK_OUT();
	return "asterisk-oh323";
}


/**
 * Call-back function to cleanup communication
 * previously established by "setup_h323_connection(...)"
 * Always returns NULL.
 */
char *cleanup_h323_connection(call_details_t cd)
{
	int i;
	char log_msg[512] = { 0 };
	char log_msg1[512] = { 0 };
	long num = 0;

	MARK_IN();
	ast_mutex_lock(&oh323_tab_lock);

	i = find_call(cd.call_token, cd.app_id);
	if (i < 0) {
		ast_log(LOG_WARNING, "Call '%s-%08x' not found (clear).\n", 
							cd.call_token, cd.app_id);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return NULL;
	}

	/* Call token match. */
	if (option_debug) {
		ast_log(LOG_DEBUG, "Call '%s-%08x' found in %d (clear).\n", 
						cd.call_token, cd.app_id, i);
	}
	if (option_debug)
		ast_log(LOG_DEBUG, "Call '%s-%08x' cleared.\n", cd.call_token, cd.app_id);

	oh323_tab[i]->cd.call_duration = cd.call_duration;
	oh323_tab[i]->cd.call_end_reason = cd.call_end_reason;
	oh323_tab[i]->cd.call_q931_cause = cd.call_q931_cause;
	oh323_tab[i]->cd.app_id = cd.app_id;
	oh323_tab[i]->cd.call_reference = cd.call_reference;
	oh323_tab[i]->ep_settings.ud.app_id = cd.app_id;
	memset(oh323_tab[i]->cd.call_token, 0, sizeof(oh323_tab[i]->cd.call_token));
	strncpy(oh323_tab[i]->cd.call_token, cd.call_token, sizeof(oh323_tab[i]->cd.call_token)-1);
	oh323_tab[i]->alreadygone = 1;
	if (option_verbose > 2) {
		if (cd.call_q931_cause) {
			snprintf(log_msg1, sizeof(log_msg1) - 1, "%d - %s", 
						cd.call_q931_cause,
						h323_get_cause_desc(cd.call_q931_cause));
			snprintf(log_msg, sizeof(log_msg) - 1, "H.323 call '%s-%08x' cleared, reason %d (%s [%s])",
						cd.call_token,
						cd.app_id,
						h323_get_reason_code(cd.call_end_reason),
						h323_get_reason_desc(cd.call_end_reason),
						log_msg1);
		} else {
			snprintf(log_msg, sizeof(log_msg) - 1, "H.323 call '%s-%08x' cleared, reason %d (%s)",
						cd.call_token,
						cd.app_id,
						h323_get_reason_code(cd.call_end_reason),
						h323_get_reason_desc(cd.call_end_reason));
		}
		if (oh323_tab[i]->established) {
			memset(log_msg1, 0, sizeof(log_msg1));
			strncpy(log_msg1, log_msg, sizeof(log_msg1) - 1);
			memset(log_msg, 0, sizeof(log_msg));
			snprintf(log_msg, sizeof(log_msg) - 1, "%s, established (%d sec)", log_msg1, cd.call_duration);
		}
		ast_verbose(VERBOSE_PREFIX_3 "%s\n", log_msg);
	}

	if (oh323_tab[i]->i_state == OH323_STATE_INIT) {
		if (option_debug)
			ast_log(LOG_DEBUG, "Call '%s-%08x' cleared in INIT state.\n",
						cd.call_token, cd.app_id);
		ISTATE_LOG(oh323_tab[i]->i_state, OH323_STATE_CLEARED);
		oh323_tab[i]->i_state = OH323_STATE_CLEARED;
	}
	if (!(oh323_tab[i]->clearing_call)) {
		oh323_tab[i]->clearing_call = OH323_CLEAR_REM;
		while ((oh323_tab[i]) && (oh323_tab[i]->owner) && 
					(ast_mutex_trylock(&(oh323_tab[i]->owner->lock)))) {
			ast_mutex_unlock(&oh323_tab_lock);
			usleep(100);
			ast_mutex_lock(&oh323_tab_lock);
			num++;
			if (num > 80000) {
				ast_log(LOG_WARNING, "Waiting too long for channel lock\n");
				CRASH;
			}
		}
		if ((oh323_tab[i]) && (oh323_tab[i]->owner)) {
			if (cd.call_q931_cause)
				oh323_tab[i]->owner->hangupcause = cd.call_q931_cause;
			ast_queue_hangup(oh323_tab[i]->owner);
			ast_mutex_unlock(&(oh323_tab[i]->owner->lock));
			if (option_debug)
				ast_log(LOG_DEBUG, "Call '%s-%08x' has been hungup.\n", cd.call_token, cd.app_id);
		}
	} else {
		oh323_tab[i]->needs_destroy = 1;
	}

	ast_mutex_unlock(&oh323_tab_lock);
	MARK_OUT();
	return NULL;
}

/**
 * Call-back function for initializing a incoming H.323 call
 * inside ASTERISK. This function limits the number of inbound
 * H.323 calls and the rate of the incoming calls.
 *
 * Returns 0 on success, -1 on error.
 */
int init_h323_connection(call_details_t cd)
{
	int	i, count, simcount, intime, index, calls_passed;
	float cur_rate;
	struct ast_channel	*c;
	char tmp1[512] = "";
	char tmp2[512] = "";
	char *user, *host, *tail;
	char chan_name[256] = "";

	MARK_IN();

	if (option_verbose > 2)
		ast_verbose(VERBOSE_PREFIX_3 "Inbound H.323 call '%s-%08x' detected.\n",
							cd.call_token, cd.app_id);

	/* Update statistics */
	ast_mutex_lock(&oh323_stats_lock);
	++oh323_stats.setup_recv;
	ast_mutex_unlock(&oh323_stats_lock);

	ast_mutex_lock(&oh323_tab_lock);

	index = in_call_rate_update();

	/* Check the number of active inbound and simultaneous H.323 calls */
	count = 0;
	simcount = 0;
	for (i=0; i<config_options.totalNum; i++)
		if (oh323_tab[i] != NULL) {
			if (oh323_tab[i]->from_remote == 1)
				++count;
			++simcount;
		}

	/* Apply call number limitations */
	if ((count >= config_options.inboundMax) ||
		((config_options.simultaneousMax > 0) && 
		(simcount >= config_options.simultaneousMax))) {

		ast_log(LOG_WARNING, "Inbound call '%s-%08x' dropped due to call-limit violation.\n",
							cd.call_token, cd.app_id);

		/* Update statistics */
		in_call_mark(index, 0);
		ast_mutex_lock(&oh323_stats_lock);
		++oh323_stats.block_incall;
		ast_mutex_unlock(&oh323_stats_lock);

		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(-1);
	}

	intime = in_call_time_get();
	calls_passed = in_call_number_passed() + 1; /* Assume that this call will
												   go through */
	if (intime > 0)
		cur_rate = (float)calls_passed * 1000 / (float)intime;
	else
		cur_rate = 0.0;
#if 0
	ast_log(LOG_DEBUG, "Cur_rate = %f, Max_rate = %f, Sim_calls = %d.\n", 
					cur_rate, in_call_max_rate, simcount);
#endif

	/* Apply call rate limitations */
	if ((simcount > config_options.crlThreshold) &&
		(cur_rate > in_call_max_rate)) {

		ast_log(LOG_WARNING, "Inbound call '%s-%08x' dropped due to in-call-rate violation (%.2f).\n",
							cd.call_token, cd.app_id, cur_rate);

		/* Update statistics */
		in_call_mark(index, 0);
		ast_mutex_lock(&oh323_stats_lock);
		++oh323_stats.block_incall;
		ast_mutex_unlock(&oh323_stats_lock);

		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(-1);
	}

	/* Get the host/user parts of the caller */
	strncpy(tmp1, cd.call_token, sizeof(tmp1) - 1);
	host = strchr(tmp1, '$');
	if (host) {
		*host = '\0';
		host++;
		tail = strchr(host, ':');
		if (tail)
			*tail = '\0';
	} else {
		ast_log(LOG_ERROR, "Cannot get host part from call token (%s)!\n", 
						cd.call_token);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(-1);
	}
	strncpy(tmp2, cd.call_source_alias, sizeof(tmp2) - 1);
	user = strchr(tmp2, ' ');
	if (user) {
		*user = '\0';
		user = tmp2;
	} else {
		memset(tmp2, 0, sizeof(tmp2));
		strncpy(tmp2, cd.call_source_e164, sizeof(tmp2) - 1);
		user = strchr(tmp2, ' ');
		if (user) {
			*user = '\0';
			user = tmp2;
		} else {
			if (strlen(cd.call_source_e164) > 0)
				user = (char *)(cd.call_source_e164);
			else
				user = NULL;
		}
	}
	if (user)
		snprintf(chan_name, sizeof(chan_name), "%s@%s", user, host);
	else
		snprintf(chan_name, sizeof(chan_name), "%s", host);


	/* Search for a free private structure entry */
	for (i=0; i<config_options.totalNum; i++)
		if (oh323_tab[i] == NULL)
			break;

	/* No free entry */
	if ((i<0) || (i>=config_options.totalNum)) {
		ast_log(LOG_ERROR, "Inbound call '%s-%08x' dropped because there is no space.\n",
							cd.call_token, cd.app_id);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(-1);
	}

	/* We found a free entry */
	oh323_tab[i] = new_oh323(i);
	if (oh323_tab[i]) {
		if (option_debug)
			ast_log(LOG_DEBUG, "Inbound call '%s-%08x' stored in entry %d.\n", 
							cd.call_token, cd.app_id, i);
		/* The call was initiated from the remote endpoint */
		oh323_tab[i]->from_remote = 1;
		in_call_mark(index, 1);

		/* Copy the call details */
		if (copy_call_details(&cd, &(oh323_tab[i]->cd)) != 0){
			ast_log(LOG_ERROR, "Failed to copy call details.\n");
			ast_mutex_unlock(&oh323_tab_lock);
			MARK_OUT();
			return(-1);
		}
		oh323_tab[i]->ep_settings.ud.app_id = cd.app_id;

		/* Attach the channel to ASTERISK PBX */
		if ((c = ast_oh323_new(oh323_tab[i], AST_STATE_RING, chan_name))) {
			if (option_verbose > 2)
				ast_verbose(VERBOSE_PREFIX_3 "Inbound H.323 call '%s-%08x', channel '%s'.\n",
								cd.call_token, cd.app_id, c->name);

			ISTATE_LOG(oh323_tab[i]->i_state, OH323_STATE_RING);
			oh323_tab[i]->i_state = OH323_STATE_RING;
			ast_mutex_unlock(&oh323_tab_lock);
			MARK_OUT();
			return(0);
		} else {
			ast_log(LOG_ERROR, "Failed to create channel for inbound call '%s-%08x'.\n",
							cd.call_token, cd.app_id);
			oh323_close_call_fds(i);
			oh323_destroy(i);
			free(oh323_tab[i]);
			oh323_tab[i] = NULL;
			ast_mutex_unlock(&oh323_tab_lock);
			MARK_OUT();
			return(-1);
		}
	}
	ast_log(LOG_WARNING, "Failed to create private structure in entry %d for inbound call '%s-%08x'.\n", 
					i, cd.call_token, cd.app_id);
	ast_mutex_unlock(&oh323_tab_lock);
	MARK_OUT();
	return(-1);
}

/**
 * Call-back function to activate the exception handling mechanism
 * of ASTERISK. Copy 'type' and 'data' into private channel's "except_struct"
 * data structure and trigger the processing of the pending exception.
 */
int exception_h323_connection(call_details_t cd, int type, char *data)
{
	int i;
	char exception_buf[2] = { OH323_EVENT_EXCE };
	struct exception_oh323 *e = NULL;
	int forward_exception = 1;

	MARK_IN();
	ast_mutex_lock(&oh323_tab_lock);

	i = find_call(cd.call_token, cd.app_id);
	if (i < 0) {
		ast_log(LOG_WARNING, "Call '%s-%08x' not found (exce).\n", 
							cd.call_token, cd.app_id);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(-1);
	}

	/* Copy important staff */
	oh323_tab[i]->cd.app_id = cd.app_id;
	oh323_tab[i]->ep_settings.ud.app_id = cd.app_id;
	oh323_tab[i]->cd.call_reference = cd.call_reference;
	memset(oh323_tab[i]->cd.call_token, 0, sizeof(oh323_tab[i]->cd.call_token));
	strncpy(oh323_tab[i]->cd.call_token, cd.call_token, sizeof(oh323_tab[i]->cd.call_token)-1);

	if (type == OH323EXC_CTRL_ERROR) {
		/* Update statistics */
		ast_mutex_lock(&oh323_stats_lock);
		++oh323_stats.proto_err;
		ast_mutex_unlock(&oh323_stats_lock);
	}

	/* Do we have an owner? */
	if (oh323_tab[i]->owner == NULL) {
		ast_log(LOG_WARNING, "Call '%s-%08x' has no owner. Autodestroying it.\n", 
						cd.call_token, cd.app_id);
		oh323_tab[i]->needs_destroy = 1;
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(0);
	}

	OBJ_ALLOC(struct exception_oh323, e);

	/* Fill the exception structure */
	switch (type) {
		case OH323EXC_USER_INPUT_TONE:
			if (option_verbose > 3)
				ast_verbose(VERBOSE_PREFIX_4 "H.323 call '%s-%08x', exception USER_INPUT (%s).\n",
								cd.call_token, cd.app_id, data);
			e->type = type;
			e->data[0] = data[0];
			break;

		case OH323EXC_USER_MESSAGE:
			if (option_verbose > 3)
				ast_verbose(VERBOSE_PREFIX_4 "H.323 call '%s-%08x', exception USER_MESSAGE (%s).\n",
								cd.call_token, cd.app_id, data);
			e->type = type;
			strncpy(e->data, data, MAX_EXCEPTION_DATA - 1);
			break;

		case OH323EXC_CALL_ALERTED:
			if (option_verbose > 3)
				ast_verbose(VERBOSE_PREFIX_4 "H.323 call '%s-%08x', exception CALL_ALERTED.\n",
								cd.call_token, cd.app_id);
			e->type = type;
			break;

		case OH323EXC_CALL_PROGRESS:
			if (option_verbose > 3)
				ast_verbose(VERBOSE_PREFIX_4 "H.323 call '%s-%08x', exception CALL_PROGRESS.\n",
								cd.call_token, cd.app_id);
			e->type = type;
			break;

		case OH323EXC_CALL_TRANSFER:
			if (option_verbose > 3)
				ast_verbose(VERBOSE_PREFIX_4 "H.323 call '%s-%08x', exception CALL_TRANSFER (%s).\n",
								cd.call_token, cd.app_id, data);
			e->type = type;
			strncpy(e->data, data, MAX_EXCEPTION_DATA - 1);
			break;

		case OH323EXC_CALL_ESTABLISHED:
			if (option_verbose > 3)
				ast_verbose(VERBOSE_PREFIX_4 "H.323 call '%s-%08x', exception CALL_ESTABLISHED.\n",
								cd.call_token, cd.app_id);
			e->type = type;
			strncpy(e->data, data, MAX_EXCEPTION_DATA - 1);
			ISTATE_LOG(oh323_tab[i]->i_state, OH323_STATE_ESTABLISHED);
			oh323_tab[i]->i_state = OH323_STATE_ESTABLISHED;

			if (oh323_tab[i]->from_remote == 1) {
				if (option_debug)
					ast_log(LOG_DEBUG, "Call '%s-%08x' established (remote).\n",
									cd.call_token, cd.app_id);
				/* Update statistics */
				ast_mutex_lock(&oh323_stats_lock);
				++oh323_stats.incall;
				ast_mutex_unlock(&oh323_stats_lock);
			} else {
				if (option_debug)
					ast_log(LOG_DEBUG, "Call '%s-%08x' established (local).\n",
									cd.call_token, cd.app_id);
				/* Update statistics */
				ast_mutex_lock(&oh323_stats_lock);
				++oh323_stats.outcall;
				ast_mutex_unlock(&oh323_stats_lock);
			}
			break;

		case OH323EXC_CTRL_ERROR:
			if (option_verbose > 3)
				ast_verbose(VERBOSE_PREFIX_4 "H.323 call '%s-%08x', exception CTRL_ERROR (%s).\n",
								cd.call_token, cd.app_id, data);
			/* Ignore these for now... */
			forward_exception = 0;
#if 0
			e->type = type;
			strncpy(e->data, data, MAX_EXCEPTION_DATA - 1);
			/* Destroy the call */
			oh323_tab[i]->needs_destroy = 1;
#endif
			break;

		default:
			ast_log(LOG_ERROR, "Call '%s-%08x' has an unknown exception %d!\n", 
								cd.call_token, cd.app_id, type);
			oh323_tab[i]->needs_destroy = 1;
			ast_mutex_unlock(&oh323_tab_lock);
			MARK_OUT();
			return(-1);
	}

	if (forward_exception) {
		OBJLIST_INSERT(&(oh323_tab[i]->exceptl), e);
		if (write(oh323_tab[i]->event_pipe[1], exception_buf, 1) != 1) {
			ast_log(LOG_WARNING, "Failed to write to event pipe (%d) for call '%s-%08x'.\n",
							type, cd.call_token, cd.app_id);
		}
	} else {
		OBJ_FREE(e);
	}

	ast_mutex_unlock(&oh323_tab_lock);

	MARK_OUT();
	return(0);
}

/**
 * This callback function is used to assign an application ID and other
 * endpoint-specified preferences on incoming H.323 calls.
 */
int get_h323_userdata(user_details_t *ud)
{
	struct oh323_ep *ptr;

	MARK_IN();
	ast_mutex_lock(&oh323_eplock);
	if (ud->incoming_call) {
		if (option_debug) {
			ast_log(LOG_DEBUG, "Request for user-specific data on incoming call.\n");
			ast_log(LOG_DEBUG, "Username: %s\n", ud->username);
			ast_log(LOG_DEBUG, "Host: %s\n", ud->ipaddr);
			ast_log(LOG_DEBUG, "Calling number: %s\n", ud->calling_num);
			ast_log(LOG_DEBUG, "Called number: %s\n", ud->called_num);
			ast_log(LOG_DEBUG, "Redirecting number: %s\n", ud->redirect_num);
		}
		ud->app_id = generate_uid();
		ptr = find_oh323_ep(NULL, ud->ipaddr, ud->username);
		if (ptr != NULL) {
			if (option_debug)
				ast_log(LOG_DEBUG, "Incoming call matches configured endpoint '%s'.\n", ptr->name);
			ud->faststart = ptr->ud.faststart;
			ud->h245tunnel = ptr->ud.h245tunnel;
			ud->h245insetup = ptr->ud.h245insetup;
			ud->prefdtmf = ptr->ud.prefdtmf;
			ud->prefcodec = ptr->ud.prefcodec;
		} else {
			if (option_debug)
				ast_log(LOG_DEBUG, "Incoming call does not match any configured endpoint.\n");
			ud->faststart = -1;
			ud->h245tunnel = -1;
			ud->h245insetup = -1;
			ud->prefdtmf = -1;
			ud->prefcodec = -1;
		}
	} else {
		ast_log(LOG_DEBUG, "Request for user-specific data on outgoing call.\n");
	}
	ast_mutex_unlock(&oh323_eplock);

	return(0);
}

int stats_h323_connection(call_details_t cd, rtp_stats_t rs)
{
/* token - pack_sent - oct_sent - pack_recv - oct_recv - pack_lost - pack_late - pack_ooo - tx_min - tx_avg - tx_max - rx_min - rx_avg - rx_max - jit_max - jit_cur - jit_cur_ms */
#define FORMAT_STRING3  "%30s %5ld %7ld %5ld %7ld %5ld %5ld %5ld (%4ld/%4ld/%4ld)ms (%4ld/%4ld/%4ld)ms (%3ld/%3ld)ms %5ld (%5ldms)\n"
#define FORMAT_STRING4  "%30s (RR) %5ld:%5ld %5ld %5ld %5ld\n"

	int i;
	rtp_stats_t *r;

	MARK_IN();
	ast_mutex_lock(&oh323_tab_lock);

	i = find_call(cd.call_token, cd.app_id);
	if (i < 0) {
		if (option_debug)
			ast_log(LOG_WARNING, "Call '%s-%08x' not found.\n", cd.call_token, cd.app_id);
		ast_mutex_unlock(&oh323_tab_lock);
		MARK_OUT();
		return(-1);
	}

	/* Call reference match. */
	(oh323_tab[i]->rtp_stats)[0] = rs;
	r = &((oh323_tab[i]->rtp_stats)[0]);
	printf(FORMAT_STRING3, 
						(oh323_tab[i]->cd.call_token != NULL ? oh323_tab[i]->cd.call_token:"NULL"),
						r->packets_sent,
						r->octets_sent,
						r->packets_recv,
						r->octets_recv,
						r->packets_lost,
						r->packets_late,
						r->packets_ooo,
						r->min_send_time,
						r->average_send_time,
						r->max_send_time,
						r->min_recv_time,
						r->average_recv_time,
						r->max_recv_time,
						r->average_jitter,
						r->max_jitter,
						r->current_jitter_size,
						(r->current_jitter_size/8));
	if (r->with_rr)
		printf(FORMAT_STRING4, 
						(oh323_tab[i]->cd.call_token != NULL ? oh323_tab[i]->cd.call_token:"NULL"),
						(r->reported_sequence & 0xffff0000) >> 16,
						(r->reported_sequence & 0xffff),
						r->reported_fraction_lost,
						r->reported_total_lost,
						r->reported_jitter);
	ast_mutex_unlock(&oh323_tab_lock);
	MARK_OUT();
	return(0);
}

/******************************************************************************/
/* Monitoring thread and queue call-back functions ****************************/

static int oh323_exec_request(const void *data)
{
	struct request_oh323 *e;
	int res, i;

	/* Exctract the first request from the list and execute it */
	OBJLIST_EXTRACT(&requestl, e);
	if (!e) {
		ast_log(LOG_ERROR, "%s called but request list is empty!\n", __FUNCTION__);
		OBJ_FREE(e);
		CRASH;
		return(0);
	}
	switch (e->type) {
	case REQ_CALL:
		ast_log(LOG_DEBUG, "Executing REQ_CALL(%s)\n", (e->call).addr);
		if (((e->call).addr)[0] == '\0') {
			ast_log(LOG_WARNING, "Request to call null H.323 destination\n");
			break;
		}
		res = h323_make_call((e->call).addr, &((e->call).cd), &((e->call).ud));
		if (res != CALL_START_OK) {
			ast_log(LOG_WARNING, "Failed to call %s.\n", (e->call).addr);
			/* Update statistics */
			ast_mutex_lock(&oh323_stats_lock);
			++oh323_stats.call_err;
			ast_mutex_unlock(&oh323_stats_lock);
		} else {
			/* Copy the call token in the right pvt */
			ast_mutex_lock(&oh323_tab_lock);
			for (i=0; i<config_options.totalNum; i++) {
				if ((oh323_tab[i] != NULL) && 
								(oh323_tab[i]->ep_settings.ud.app_id == (e->call).ud.app_id)) {
					ast_log(LOG_DEBUG, "REQ_CALL(%s): Copying call token '%s' in call with ID %08x\n", 
									(e->call).addr, (e->call).cd.call_token, (e->call).ud.app_id);
					memset(oh323_tab[i]->cd.call_token, 0, sizeof(oh323_tab[i]->cd.call_token));
					strncpy(oh323_tab[i]->cd.call_token, (e->call).cd.call_token, sizeof(oh323_tab[i]->cd.call_token)-1);
				}
			}
			ast_mutex_unlock(&oh323_tab_lock);
			/* Update statistics */
			ast_mutex_lock(&oh323_stats_lock);
			++oh323_stats.setup_sent;
			ast_mutex_unlock(&oh323_stats_lock);
		}
		break;
	case REQ_ANSWER:
		ast_log(LOG_DEBUG, "Executing REQ_ANSWER(%s)\n", (e->answer).token);
		if (((e->answer).token)[0] == '\0') {
			ast_log(LOG_WARNING, "Request to answer call with null token\n");
			break;
		}
		res = h323_answer_call((e->answer).token);
		if (res != CALL_ANS_OK) {
			ast_log(LOG_WARNING, "Failed to answer call with token %s.\n", (e->answer).token);
			/* Update statistics */
			ast_mutex_lock(&oh323_stats_lock);
			++oh323_stats.answer_err;
			ast_mutex_unlock(&oh323_stats_lock);
		}
		break;
	case REQ_DIGIT:
		ast_log(LOG_DEBUG, "Executing REQ_DIGIT(%s, %c)\n", (e->digit).token, (e->digit).digit);
		if (((e->digit).token)[0] == '\0') {
			ast_log(LOG_WARNING, "Request to send digit to call with null token\n");
			break;
		}
		h323_send_tone((e->digit).token, (e->digit).digit);
		break;
	case REQ_TEXT:
		ast_log(LOG_DEBUG, "Executing REQ_TEXT(%s, %s)\n", (e->text).token, (e->text).text);
		if (((e->text).token)[0] == '\0') {
			ast_log(LOG_WARNING, "Request to send text to call with null token\n");
			break;
		}
		h323_send_text((e->text).token, (e->text).text);
		break;
	case REQ_INDICATE:
		ast_log(LOG_DEBUG, "Executing REQ_INDICATE(%s, %d)\n", (e->indication).token, (e->indication).type);
		if (((e->indication).token)[0] == '\0') {
			ast_log(LOG_WARNING, "Request to indicate condition on call with null token\n");
			break;
		}
		res = h323_indicate_call((e->indication).token, (e->indication).type);
		if (res != CALL_IND_OK) {
			ast_log(LOG_WARNING, "Failed to indicate %d on call with token %s.\n", 
							(e->indication).type, (e->indication).token);
		}
		break;
	case REQ_HANGUP:
		ast_log(LOG_DEBUG, "Executing REQ_HANGUP(%s, %d)\n", (e->hangup).token, (e->hangup).q931_cause);
		if (((e->hangup).token)[0] == '\0') {
			ast_log(LOG_WARNING, "Request to hangup call with null token\n");
			break;
		}
		if ((e->hangup).q931_cause)
			h323_set_hangup_cause((e->hangup).token, (e->hangup).q931_cause);
		h323_clear_call((e->hangup).token);
		break;
	default:
		ast_log(LOG_WARNING, "Unknown request (%d)\n", e->type);
		break;
	}
	OBJ_FREE(e);
	return(0);
}

static int oh323_release(const void *data)
{
	struct chan_oh323_pvt *pvt = (struct chan_oh323_pvt *)data;
	int index;

	if (!pvt)
		return(0);

	ast_mutex_lock(&oh323_tab_lock);
	if (pvt->owner) {
		ast_log(LOG_WARNING, "Call '%s' is owned?\n", pvt->cd.call_token);
		ast_mutex_unlock(&oh323_tab_lock);
		return(0);
	}
	index = pvt->index;
	ast_log(LOG_WARNING, "Forcing the release of entry %d (call '%s').\n", 
					index, pvt->cd.call_token);
	pvt->needs_destroy = 1;
	pvt->sched_id = -1;
	ast_mutex_unlock(&oh323_tab_lock);
	return(0);
}

static int oh323_gk_check(const void *data)
{
	char gkname[256];

	if (config_options.gatekeeperMode != GKMODE_DISABLE) {
		switch (h323_get_gk(gkname, sizeof(gkname))) {
		case 0:
			ast_verbose(VERBOSE_PREFIX_3 "Registered with gatekeeper '%s'.\n",
							(char*)gkname);
			break;
		case OH323GK_FAILED:
			ast_verbose(VERBOSE_PREFIX_3 "Failed to get gatekeeper status.\n");
			break;
		case OH323GK_NOTREGISTERED:
			ast_verbose(VERBOSE_PREFIX_3 "Gatekeeper '%s' found but failed to register.\n",
							(char*)gkname);
			break;
		case OH323GK_NOTUSED:
			if (config_options.gatekeeperMode == GKMODE_DISCOVER) {
				ast_log(LOG_WARNING, "Gatekeeper discovery failed.\n");
			} else {
				ast_log(LOG_WARNING, "Failed to register with gatekeeper '%s'.\n",
							(char*)config_options.gatekeeperName);
			}
			/* Retry the gatekeeper discovery/registration */
			ast_verbose(VERBOSE_PREFIX_3 "Retrying gatekeeper registration.\n");
			if (h323_reset_gk(config_options.gatekeeperName,
						config_options.gatekeeperZone)) {
				ast_log(LOG_ERROR, "H.323 gatekeeper setup failed.\n");
				return(0);
			}
			/* Reschedule */
			return(1);
		}
	}
	/* Do not reschedule the gatekeeper discovery/registration */
	return(0);
}

/**
 * The monitor thread.
 * This thread executes all the scheduled actions and supervises
 * the active channels.
 */
static void *do_monitor(void *ignore)
{
	struct timeval tv;
	int res, i;

	if (option_debug)
		ast_log(LOG_DEBUG, "Monitor thread started.\n");

	while (1) {
		/* <ENDLESS LOOP> */
		if (ast_mutex_lock(&mon_lock)) {
			ast_log(LOG_ERROR, "Unable to grab monitor lock.\n");
			monitor_running = 0;
			break;
		}
		monitor_running = 1;
		/* Check for a request to die */
		if (monitor_shutdown) {
			if (option_debug)
				ast_log(LOG_DEBUG, "Monitor thread is going down.\n");
			monitor_running = 0;
			ast_mutex_unlock(&mon_lock);
			break;
		}
		ast_mutex_unlock(&mon_lock);

		/* Wait some time */
		tv.tv_sec = 0;
		tv.tv_usec = 654321;
		res = select(0, NULL, NULL, NULL, &tv);
		if (res < 0) {
			if ((errno != EAGAIN) && (errno != EINTR))
				ast_log(LOG_WARNING, "Select failed: %s.\n", strerror(errno));
			continue;
		} else {
			ast_mutex_lock(&mon_lock);

			/* Check and process the run queue */
			ast_sched_runq(oh323_sched);

			/* Check the existing channels */
			ast_mutex_lock(&oh323_tab_lock);
			for (i=0; i<config_options.totalNum; i++)
				if ((oh323_tab[i] != NULL) && (oh323_tab[i]->needs_destroy)) {
					if (oh323_tab[i]->owner == NULL) {
						if (option_debug) {
							if (oh323_tab[i]->clearing_call) {
								ast_log(LOG_DEBUG, "Call '%s' without owner has already been cleared (%d).\n",
											oh323_tab[i]->cd.call_token, oh323_tab[i]->clearing_call);
							} else {
								ast_log(LOG_DEBUG, "Call '%s' without owner not already cleared???\n",
											oh323_tab[i]->cd.call_token);
							}
						}
						if (oh323_tab[i]->sched_id != -1) {
							ast_sched_del(oh323_sched, oh323_tab[i]->sched_id);
							if (option_debug)
								ast_log(LOG_DEBUG, "Cancelled scheduled release of call '%s'.\n", 
											oh323_tab[i]->cd.call_token);
						}
						/* Deallocate the resources */
						oh323_close_call_fds(i);
						oh323_destroy(i);
						free(oh323_tab[i]);
						oh323_tab[i] = NULL;
					} else {
						ast_log(LOG_DEBUG, "Call '%s' with owner '%s' needs destroy.\n",
											oh323_tab[i]->cd.call_token, oh323_tab[i]->owner->name);
					}
				}
			ast_mutex_unlock(&oh323_tab_lock);

			ast_mutex_unlock(&mon_lock);
		}
		/* </ENDLESS LOOP> */
	}
	sched_context_destroy(oh323_sched);
	return NULL;
}


/**
 * Restart the monitor thread.
 */
static int restart_monitor(void)
{
	/* If we're supposed to be stopped -- stay stopped */
	if (monitor_thread == AST_PTHREADT_STOP)
		return 0;
	if (ast_mutex_lock(&mon_lock)) {
		ast_log(LOG_WARNING, "Unable to lock monitor.\n");
		return -1;
	}
	if (monitor_thread == pthread_self()) {
		ast_mutex_unlock(&mon_lock);
		ast_log(LOG_WARNING, "Cannot kill myself.\n");
		return -1;
	}
	if (monitor_thread) {
		/* Wake up the thread */
		pthread_kill(monitor_thread, SIGURG);
		if (option_debug)
			ast_log(LOG_DEBUG, "Monitor thread restarted.\n");
	} else {
		/* Start a new monitor */
		if (ast_pthread_create(&monitor_thread, NULL, do_monitor, NULL) < 0) {
			ast_mutex_unlock(&mon_lock);
			ast_log(LOG_ERROR, "Unable to start monitor thread.\n");
			return -1;
		}
		if (option_debug)
			ast_log(LOG_DEBUG, "New monitor thread started.\n");
	}
	ast_mutex_unlock(&mon_lock);
	return 0;
}

/**
 * Kill the monitor thread.
 */
static int kill_monitor(void)
{
	struct timeval tv;
	int maxtries = 5;
	int res;

	if (!ast_mutex_lock(&mon_lock)) {
		if (!monitor_running)
			return(0);
		if (monitor_thread) {
			monitor_shutdown = 1;
			pthread_kill(monitor_thread, SIGURG);
		}
		ast_mutex_unlock(&mon_lock);
		usleep(100);
		if (option_debug)
			ast_log(LOG_DEBUG, "Waiting monitor thread to come down...\n");
		while (1) {
			ast_mutex_lock(&mon_lock);
			if (!monitor_running) {
				if (option_debug)
					ast_log(LOG_DEBUG, "Monitor thread terminated.\n");
				ast_mutex_unlock(&mon_lock);
				break;
			}
			ast_mutex_unlock(&mon_lock);
			tv.tv_sec = 0;
			tv.tv_usec = 100000;
			res = select(0, NULL, NULL, NULL, &tv);
			if (res < 0) {
				if ((errno != EAGAIN) && (errno != EINTR))
					ast_log(LOG_WARNING, "Select failed: %s.\n", strerror(errno));
			}
			--maxtries;
			if (maxtries < 0) {
				ast_log(LOG_WARNING, "Failed to kill monitor thread.\n");
				return(-1);
			}
		}
		monitor_thread = -2;
	} else {
		ast_log(LOG_WARNING, "Unable to lock the monitor.\n");
		return(-1);
	}
	return(0);
}


/******************************************************************************/
/* Configuration file parsing *************************************************/

static int init_registry_info(struct ast_config *cfg, struct oh323_reginfo **reg,
							  char *default_context)
{
	struct ast_variable	*v = NULL;
	struct oh323_reginfo *regtmp, *regprev;
	int nocontext;

	if ((cfg == NULL)||(reg == NULL)||(default_context == NULL))
		return(-1);
	nocontext = 1;
	*reg = NULL;
	regtmp = NULL;
	regprev = NULL;
	v = ast_variable_browse(cfg, "register");
	while (v) {
		if (!strcasecmp(v->name, "context")) {
			if (regtmp != NULL) {
				if (regtmp->alias_num > 0) {
					regtmp->alias = (char **)malloc(regtmp->alias_num * sizeof(char*));
					if (regtmp->alias == NULL) {
						ast_log(LOG_ERROR, "Memory allocation failed.\n");
						return(-1);
					}
					memset(regtmp->alias, 0, regtmp->alias_num * sizeof(char*));
				}
				if (regtmp->prefix_num > 0) {
					regtmp->prefix = (char **)malloc(regtmp->prefix_num * sizeof(char*));
					if (regtmp->prefix == NULL) {
						ast_log(LOG_ERROR, "Memory allocation failed.\n");
						return(-1);
					}
					memset(regtmp->prefix, 0, regtmp->prefix_num * sizeof(char*));
				}
				regprev = regtmp;
			}
			/* Allocate a new struct */
			regtmp = (struct oh323_reginfo *)malloc(sizeof(struct oh323_reginfo));
			if (regtmp == NULL) {
				ast_log(LOG_ERROR, "Memory allocation failed.\n");
				return(-1);
			}
			memset(regtmp, 0, sizeof(struct oh323_reginfo));
			if (*reg == NULL)	/* Save the head of the list */
				*reg = regtmp;
			else				/* Connect the new struct to the end of the list */
				regprev->next = regtmp;
			strncpy(regtmp->context, v->value, AST_MAX_EXTENSION-1);
			nocontext = 0;
		} else if (!strcasecmp(v->name, "alias")) {
			if ((nocontext)&&(regtmp == NULL)) {
				/* Allocate a new struct */
				regtmp = (struct oh323_reginfo *)malloc(sizeof(struct oh323_reginfo));
				if (regtmp == NULL) {
					ast_log(LOG_ERROR, "Memory allocation failed.\n");
					return(-1);
				}
				memset(regtmp, 0, sizeof(struct oh323_reginfo));
				*reg = regtmp; /* Save the head of the list */
				strncpy(regtmp->context, default_context, AST_MAX_EXTENSION-1);
			}
			++regtmp->alias_num;
		} else if (!strcasecmp(v->name, "gwprefix")) {
			if ((nocontext)&&(regtmp == NULL)) {
				/* Allocate a new struct */
				regtmp = (struct oh323_reginfo *)malloc(sizeof(struct oh323_reginfo));
				if (regtmp == NULL) {
					ast_log(LOG_ERROR, "Memory allocation failed.\n");
					return(-1);
				}
				memset(regtmp, 0, sizeof(struct oh323_reginfo));
				*reg = regtmp; /* Save the head of the list */
				strncpy(regtmp->context, default_context, AST_MAX_EXTENSION-1);
			}
			++regtmp->prefix_num;
		} else
			ast_log(LOG_NOTICE, "Ignoring unknown H.323 [register] keyword '%s', line %d.\n", 
								v->name, v->lineno);
		v = v->next;
	}
	/* Update the last struct of the list */
	if (regtmp != NULL) {
		if (regtmp->alias_num > 0) {
			regtmp->alias = (char **)malloc(regtmp->alias_num * sizeof(char*));
			if (regtmp->alias == NULL) {
				ast_log(LOG_ERROR, "Memory allocation failed.\n");
				return(-1);
			}
			memset(regtmp->alias, 0, regtmp->alias_num * sizeof(char*));
		}
		if (regtmp->prefix_num > 0) {
			regtmp->prefix = (char **)malloc(regtmp->prefix_num * sizeof(char*));
			if (regtmp->prefix == NULL) {
				ast_log(LOG_ERROR, "Memory allocation failed.\n");
				return(-1);
			}
			memset(regtmp->prefix, 0, regtmp->prefix_num * sizeof(char*));
		}
		regtmp->next = NULL;
	}

	return(0);
}

static int reload_config(void)
{
	struct ast_config *cfg = NULL;
	struct ast_variable	*v = NULL;
	struct oh323_reginfo *regtmp;
	struct oh323_codecinfo *codectmp, *codecprev = NULL;
	struct oh323_ep *extprev, *exttmp;
	char *s;
	int format, alias_index, prefix_index;
	int codec_found, frames_found, id_count;
	int vcodec, flags, dtmfmode;

	/* Initialize stuff */
	memset((char *)&config_options, 0, sizeof(config_options));
	config_options.listenPort = 1720;
	config_options.tcpStart = 5000;
	config_options.tcpEnd = 31000;
	config_options.udpStart = 5000;
	config_options.udpEnd = 31000;
	config_options.rtpStart = 5000;
	config_options.rtpEnd = 31000;
	config_options.fastStart = 0;
	config_options.h245Tunnelling = 0;
	config_options.h245inSetup = 0;
	config_options.jitterMin = 20;		/* ms */
	config_options.jitterMax = 100;		/* ms */
#ifdef HAS_OH323MODS
	config_options.jitterDec = 2;		/* frames */
	config_options.jitterInc = 1;		/* frames */
#endif
	config_options.ipTos = 0;
	config_options.outboundMax = 0;
	config_options.inboundMax = 0;
	config_options.simultaneousMax = -1;
	config_options.totalNum = 0;
	config_options.bwlimit = 1024*1024;		/* 1 Mbit/sec */
	config_options.crlCallNumber = MAX_INCALLS;
	config_options.crlCallTime = MAX_INCALLTIME;
	config_options.crlThreshold = MAX_INCALLTIME_CALLTHRESHOLD;
	config_options.wrapLibTraceLevel = 0;
	config_options.libTraceLevel = 0;
	strncpy(config_options.libTraceFile, "stdout", 
					sizeof(config_options.libTraceFile)-1);
	config_options.amaFlags = 0;
	config_options.gatekeeperMode = GKMODE_DISABLE;
	config_options.gatekeeperTTL = 0;		/* for ever */
	config_options.userInputMode = UIMODE_TONE;
	strncpy(config_options.context, "default", AST_MAX_EXTENSION-1);
	oh323_capability = 0;

	/* Get the RTP port range from "rtp.conf" */
	cfg = ast_config_load("rtp.conf");
	if (cfg) {
		if ((s = (char *)ast_variable_retrieve(cfg, "general", "rtpstart"))) {
			config_options.rtpStart = atoi(s);
			if (config_options.rtpStart < 1024)
				config_options.rtpStart = 1024;
			if (config_options.rtpStart > 65535)
				config_options.rtpStart = 65535;
		}
		if ((s = (char *)ast_variable_retrieve(cfg, "general", "rtpend"))) {
			config_options.rtpEnd = atoi(s);
			if (config_options.rtpEnd < 1024)
				config_options.rtpEnd = 1024;
			if (config_options.rtpEnd > 65535)
				config_options.rtpEnd = 65535;
		}
		ast_config_destroy(cfg);
	}

	/* We must have a config file, otherwise stop */
	cfg = ast_config_load(config);
	if (!cfg) {
		ast_log(LOG_ERROR, "Unable to load configuration file (%s).\n", config);
		return(-1);
	}

	/* There must be a [general] category, otherwise stop */
	v = ast_variable_browse(cfg, "general");
	if (v == NULL) {
		ast_log(LOG_ERROR, "Category [general] not present in configuration file (%s).\n", config);
		return(-1);
	}
	while (v) {
		if (!strcasecmp(v->name, "listenAddress")) {
			strncpy(config_options.listenAddress, v->value, 
							sizeof(config_options.listenAddress)-1);
		} else if (!strcasecmp(v->name, "listenPort")) {
			config_options.listenPort = (int) strtol(v->value, NULL, 10);
		} else if (!strcasecmp(v->name, "tcpStart")) {
			config_options.tcpStart = (int) strtol(v->value, NULL, 10);
		} else if (!strcasecmp(v->name, "tcpEnd")) {
			config_options.tcpEnd = (int) strtol(v->value, NULL, 10);
		} else if (!strcasecmp(v->name, "udpStart")) {
			config_options.udpStart = (int) strtol(v->value, NULL, 10);
		} else if (!strcasecmp(v->name, "udpEnd")) {
			config_options.udpEnd = (int) strtol(v->value, NULL, 10);
		} else if (!strcasecmp(v->name, "fastStart")) {
			config_options.fastStart = (ast_true(v->value) == 0 ? 0 : 1);
		} else if (!strcasecmp(v->name, "h245Tunnelling")) {
			config_options.h245Tunnelling = (ast_true(v->value) == 0 ? 0 : 1);
		} else if (!strcasecmp(v->name, "h245inSetup")) {
			config_options.h245inSetup = (ast_true(v->value) == 0 ? 0 : 1);
		} else if (!strcasecmp(v->name, "jitterMin")) {
			config_options.jitterMin = (int) strtol(v->value, NULL, 10);
			if (config_options.jitterMin < 20) {
				ast_log(LOG_WARNING, "Invalid min jitter value (<20ms)! Setting it to 20ms.\n");
				config_options.jitterMin = 20;
			} else if (config_options.jitterMin > 10000) {
				ast_log(LOG_WARNING, "Invalid min jitter value (>10s)! Setting it to 10s.\n");
				config_options.jitterMin = 10000;
			}
		} else if (!strcasecmp(v->name, "jitterMax")) {
			config_options.jitterMax = (int) strtol(v->value, NULL, 10);
			if (config_options.jitterMax < 20) {
				ast_log(LOG_WARNING, "Invalid max jitter value (<20ms)! Setting it to 20ms.\n");
				config_options.jitterMax = 20;
			} else if (config_options.jitterMax > 10000) {
				ast_log(LOG_WARNING, "Invalid max jitter value (>10s)! Setting it to 10s.\n");
				config_options.jitterMax = 10000;
			}
#ifdef	HAS_OH323MODS
		} else if (!strcasecmp(v->name, "jitterDec")) {
			config_options.jitterDec = (int) strtol(v->value, NULL, 10);
		} else if (!strcasecmp(v->name, "jitterInc")) {
			config_options.jitterInc = (int) strtol(v->value, NULL, 10);
#endif
		} else if (!strcasecmp(v->name, "ipTos")) {
			if (sscanf(v->value, "%i", &format) == 1)
				config_options.ipTos = format & 0xff;
			else if (!strcasecmp(v->value, "lowdelay"))
				config_options.ipTos = IPTOS_LOWDELAY;
			else if (!strcasecmp(v->value, "throughput"))
				config_options.ipTos = IPTOS_THROUGHPUT;
			else if (!strcasecmp(v->value, "reliability"))
				config_options.ipTos = IPTOS_RELIABILITY;
			else if (!strcasecmp(v->value, "mincost"))
				config_options.ipTos = IPTOS_MINCOST;
			else if (!strcasecmp(v->value, "none"))
				config_options.ipTos = 0;
			else
				ast_log(LOG_WARNING, "Invalid ToS value (%s) at line %d, "
								"should be 'lowdelay', 'throughput', "
								"'reliability', 'mincost', or 'none'.\n",
								v->value, v->lineno);
		} else if (!strcasecmp(v->name, "outboundMax")) {
			config_options.outboundMax = (int) strtol(v->value, NULL, 10);
			if (config_options.outboundMax < 0) {
				ast_log(LOG_WARNING, "Invalid max number of outbound H.323 connections (<0)! Setting it to 0.\n");
				config_options.outboundMax = 0;
			}
		} else if (!strcasecmp(v->name, "inboundMax")) {
			config_options.inboundMax = (int) strtol(v->value, NULL, 10);
			if (config_options.inboundMax < 0) {
				ast_log(LOG_WARNING, "Invalid max number of inbound H.323 connections (<0)! Setting it to 0.\n");
				config_options.inboundMax = 0;
			}
		} else if (!strcasecmp(v->name, "simultaneousMax")) {
			config_options.simultaneousMax = (int) strtol(v->value, NULL, 10);
			if (config_options.simultaneousMax < 0) {
				ast_log(LOG_WARNING, "Invalid max number of simultaneous H.323 connections (<0)! Setting it to 0.\n");
				config_options.simultaneousMax = 0;
			}
		} else if (!strcasecmp(v->name, "bandwidthLimit")) {
			config_options.bwlimit = (int) strtol(v->value, NULL, 10);
			config_options.bwlimit = (int) ((float)config_options.bwlimit*(1024.0/100.0));
			if (config_options.bwlimit < 0) {
				ast_log(LOG_WARNING, "Invalid bandwidth limit value for H.323 connections (<0)! Setting it to 0.\n");
				config_options.bwlimit = 0;
			}
		} else if (!strcasecmp(v->name, "crlCallNumber")) {
			config_options.crlCallNumber = (int) strtol(v->value, NULL, 10);
			if (config_options.crlCallNumber < 0) {
				ast_log(LOG_WARNING, "Invalid call rate limiter parameter (crlCallNumber)! Setting it to 0.\n");
				config_options.crlCallNumber = 0;
			}
		} else if (!strcasecmp(v->name, "crlCallTime")) {
			config_options.crlCallTime = (int) strtol(v->value, NULL, 10);
			if (config_options.crlCallTime < 0) {
				ast_log(LOG_WARNING, "Invalid call rate limiter parameter (crlCallTime)! Setting it to 0.\n");
				config_options.crlCallTime = 0;
			}
		} else if (!strcasecmp(v->name, "crlThreshold")) {
			config_options.crlThreshold = (int) strtol(v->value, NULL, 10);
			if (config_options.crlThreshold < 0) {
				ast_log(LOG_WARNING, "Invalid call rate limiter parameter (crlThreshold)! Setting it to 0.\n");
				config_options.crlThreshold = 0;
			}
		} else if (!strcasecmp(v->name, "wrapLibTraceLevel")) {
			config_options.wrapLibTraceLevel = (int) strtol(v->value, NULL, 10);
			if (config_options.wrapLibTraceLevel < 0) {
				ast_log(LOG_WARNING, "Invalid wrapper library trace level! Disabling it.\n");
				config_options.wrapLibTraceLevel = 0;
			}
		} else if (!strcasecmp(v->name, "libTraceLevel")) {
			config_options.libTraceLevel = (int) strtol(v->value, NULL, 10);
			if (config_options.libTraceLevel < 0) {
				ast_log(LOG_WARNING, "Invalid library trace level! Disabling it.\n");
				config_options.libTraceLevel = 0;
			}
		} else if (!strcasecmp(v->name, "libTraceFile")) {
			if ((v->value[0] == '\0') || (!strcasecmp(v->value, "stdout")))
				memset(config_options.libTraceFile, 0, sizeof(config_options.libTraceFile));
			else
				strncpy(config_options.libTraceFile, v->value, 
								sizeof(config_options.libTraceFile)-1);
		} else if (!strcasecmp(v->name, "accountCode")) {
			strncpy(config_options.accountCode, v->value, 
								sizeof(config_options.accountCode)-1);
		} else if (!strcasecmp(v->name, "amaFlags")) {
			flags = ast_cdr_amaflags2int(v->value);
			if (flags < 0) {
				ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
			} else {
				config_options.amaFlags = flags;
			}
		} else if (!strcasecmp(v->name, "language")) {
			strncpy(config_options.language, v->value, 
								sizeof(config_options.language)-1);
		} else if (!strcasecmp(v->name, "musiconhold")) {
			strncpy(config_options.musicclass, v->value, 
								sizeof(config_options.musicclass)-1);
		} else if (!strcasecmp(v->name, "gatekeeper")) {
			if (!strcasecmp(v->value, "DISABLE")) {
				config_options.gatekeeperMode = GKMODE_DISABLE;
			} else if (!strcasecmp(v->value, "DISCOVER")) {
				config_options.gatekeeperMode = GKMODE_DISCOVER;
			} else if (!strncasecmp(v->value, "GKID:", 5)){
				config_options.gatekeeperMode = GKMODE_ID;
				strncpy(config_options.gatekeeperZone, (char*)(v->value+5), 
								sizeof(config_options.gatekeeperZone)-1);
			} else {
				char *r;
				r = strchr(v->value, '@');
				if (!r) {
					config_options.gatekeeperMode = GKMODE_NAME;
					strncpy(config_options.gatekeeperName, v->value, 
								sizeof(config_options.gatekeeperName)-1);
				} else {
					*r = '\0';
					r++;
					if ((v->value == NULL) || (strlen(v->value) == 0) 
						|| (r == NULL) || (strlen(r) == 0)) {
						ast_log(LOG_WARNING, "Invalid gatekeeper: %s at line %d\n", 
										v->value, v->lineno);
					} else {
						config_options.gatekeeperMode = GKMODE_NAME_ID;
						strncpy(config_options.gatekeeperZone, v->value, 
									sizeof(config_options.gatekeeperZone)-1);
						strncpy(config_options.gatekeeperName, r, 
									sizeof(config_options.gatekeeperName)-1);
					}
				}
			}
		} else if (!strcasecmp(v->name, "gatekeeperPassword")) {
			strncpy(config_options.gatekeeperPass, v->value, 
							sizeof(config_options.gatekeeperPass)-1);
		} else if (!strcasecmp(v->name, "gatekeeperTTL")) {
			config_options.gatekeeperTTL = (int) strtol(v->value, NULL, 10);
			if (config_options.gatekeeperTTL < 0) {
				ast_log(LOG_WARNING, "Invalid gatekeeper TTL value %s at line %d\n", 
								v->value, v->lineno);
				config_options.gatekeeperTTL = 0;
			}
		} else if (!strcasecmp(v->name, "userInputMode")) {
			dtmfmode = get_num(dtmf_str, v->value);
			if (dtmfmode < 0) {
				ast_log(LOG_WARNING, "Unknown DTMF mode '%s'.\n", v->value);
			} else {
				config_options.userInputMode = dtmfmode;
			}
		} else if (!strcasecmp(v->name, "context")) {
			strncpy(config_options.context, v->value, sizeof(config_options.context)-1);
		} else
			ast_log(LOG_NOTICE, "Ignoring unknown H.323 [general] keyword '%s', line %d.\n", 
								v->name, v->lineno);
		v = v->next;
	}

	if (config_options.tcpStart >= config_options.tcpEnd) {
		ast_log(LOG_WARNING, "Invalid TCP port values (start=%d, end=%d). Resetting to defaults.\n",
						config_options.tcpStart, config_options.tcpEnd);
		config_options.tcpStart = 5000;
		config_options.tcpEnd = 31000;
	}
	if (config_options.udpStart >= config_options.udpEnd) {
		ast_log(LOG_WARNING, "Invalid UDP port values (start=%d, end=%d). Resetting to defaults.\n",
						config_options.udpStart, config_options.udpEnd);
		config_options.udpStart = 5000;
		config_options.udpEnd = 31000;
	}
	if (config_options.jitterMin > config_options.jitterMax) {
		ast_log(LOG_WARNING, "Invalid jitter buffer values (min=%d, max=%d). Resetting to defaults.\n",
						config_options.jitterMin, config_options.jitterMax);
		config_options.jitterMin = 20;
		config_options.jitterMax = 100;
	}
	/* XXX We should restrict this number to an upper limit */
	config_options.totalNum = config_options.inboundMax+config_options.outboundMax;
	if (config_options.simultaneousMax > -1) {
		if (config_options.simultaneousMax > config_options.totalNum) {
			ast_log(LOG_NOTICE, "Value %d of max simultaneous H.323 calls "
						"doesn't make sense (max total number of H.323 calls "
						"is %d). Setting it %d.\n", 
						config_options.simultaneousMax,
						config_options.totalNum, config_options.totalNum);
			config_options.simultaneousMax = config_options.totalNum;
		}
	}

	/* Check for a [register] category */
	if (init_registry_info(cfg, &(config_options.regInfo), 
							config_options.context) != 0) {
		return(-1);
	}
	if (config_options.regInfo) {
		regtmp = config_options.regInfo;
		alias_index = 0;
		prefix_index = 0;
		v = ast_variable_browse(cfg, "register");
		while (v) {
			if ((alias_index == regtmp->alias_num)&&(prefix_index == regtmp->prefix_num)) {
				regtmp = regtmp->next;
				alias_index = 0;
				prefix_index = 0;
			}
			if (!strcasecmp(v->name, "alias")) {
				regtmp->alias[alias_index] = (char *)malloc(strlen(v->value)+1);
				if (regtmp->alias[alias_index] == NULL) {
					ast_log(LOG_ERROR, "Memory allocation failed.\n");
					return(-1);
				}
				memset(regtmp->alias[alias_index], 0, strlen(v->value)+1);
				strcpy(regtmp->alias[alias_index], v->value);
				++alias_index;
			} else if (!strcasecmp(v->name, "gwprefix")) {
				regtmp->prefix[prefix_index] = (char *)malloc(strlen(v->value)+1);
				if (regtmp->prefix[prefix_index] == NULL) {
					ast_log(LOG_ERROR, "Memory allocation failed.\n");
					return(-1);
				}
				memset(regtmp->prefix[prefix_index], 0, strlen(v->value)+1);
				strcpy(regtmp->prefix[prefix_index], v->value);
				++prefix_index;
			}
			v = v->next;
		}
	} else {
		ast_log(LOG_NOTICE, "H.323 [register] category contains no entries.\n");
	}

	/* Check for a [endpoints] category */
	id_count = 0;
	exttmp = NULL;
	extprev = NULL;
	v = ast_variable_browse(cfg, "endpoints");
	while (v) {
		if (!strcasecmp(v->name, "name")) {
			if (exttmp != NULL)
				extprev = exttmp;
			exttmp = (struct oh323_ep *)malloc(sizeof(struct oh323_ep));
			if (exttmp == NULL) {
				ast_log(LOG_ERROR, "Memory allocation failed.\n");
				return(-1);
			}
			memset((char*)exttmp, 0, sizeof(struct oh323_ep));
			strncpy(exttmp->name, v->value, sizeof(exttmp->name)-1);
			exttmp->ud.faststart = -1;
			exttmp->ud.h245tunnel = -1;
			exttmp->ud.h245insetup = -1;
			exttmp->ud.prefcodec = -1;
			exttmp->ud.prefdtmf = -1;
			exttmp->next = NULL;
			if (oh323_eplist == NULL) /* Save the head */
				oh323_eplist = exttmp;
			else /* Connect the next element */
				extprev->next = exttmp;
			id_count++;
		} else if (!strcasecmp(v->name, "host")) {
			if (exttmp)
				strncpy(exttmp->ud.ipaddr, v->value, sizeof(exttmp->ud.ipaddr)-1);
		} else if (!strcasecmp(v->name, "port")) {
			if (exttmp)
				exttmp->ud.port = (int) strtol(v->value, NULL, 10);
		} else if (!strcasecmp(v->name, "username")) {
			if (exttmp)
				strncpy(exttmp->ud.username, v->value, sizeof(exttmp->ud.username)-1);
		} else if (!strcasecmp(v->name, "context")) {
			if (exttmp)
				strncpy(exttmp->context, v->value, sizeof(exttmp->context)-1);
		} else if (!strcasecmp(v->name, "language")) {
			if (exttmp)
				strncpy(exttmp->language, v->value, sizeof(exttmp->language)-1);
		} else if (!strcasecmp(v->name, "musiconhold")) {
			if (exttmp)
				strncpy(exttmp->musicclass, v->value, sizeof(exttmp->musicclass)-1);
		} else if (!strcasecmp(v->name, "amaflags")) {
			flags = ast_cdr_amaflags2int(v->value);
			if (flags < 0) {
				ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno);
			} else {
				if (exttmp)
					exttmp->amaflags = flags;
			}
		} else if (!strcasecmp(v->name, "accountcode")) {
			if (exttmp)
				strncpy(exttmp->accountcode, v->value, sizeof(exttmp->accountcode)-1);
		} else if (!strcasecmp(v->name, "faststart")) {
			if (exttmp)
				exttmp->ud.faststart = (ast_true(v->value) == 0 ? 0 : 1);
		} else if (!strcasecmp(v->name, "h245tunnelling")) {
			if (exttmp)
				exttmp->ud.h245tunnel = (ast_true(v->value) == 0 ? 0 : 1);
		} else if (!strcasecmp(v->name, "h245insetup")) {
			if (exttmp)
				exttmp->ud.h245insetup = (ast_true(v->value) == 0 ? 0 : 1);
		} else if (!strcasecmp(v->name, "callingnum")) {
			if (exttmp)
				strncpy(exttmp->ud.calling_num, v->value, sizeof(exttmp->ud.calling_num)-1);
		} else if (!strcasecmp(v->name, "prefcodec")) {
			vcodec = get_num(codec_str, v->value);
			if (vcodec == 0) {
				ast_log(LOG_WARNING, "Unknown codec '%s'.\n", v->value);
			} else {
				format = oh323_codec2format(vcodec);
				if (format == 0) {
					ast_log(LOG_WARNING, "Unsupported codec '%s'.\n", v->value);
				} else {
					exttmp->ud.prefcodec = vcodec;
				}
			}
		} else if (!strcasecmp(v->name, "prefdtmfmode")) {
			if (exttmp) {
				dtmfmode = get_num(dtmf_str, v->value);
				if (dtmfmode < 0) {
					ast_log(LOG_WARNING, "Unknown DTMF mode '%s' for endpoint '%s'.\n", 
									v->value, exttmp->name);
				} else {
					 exttmp->ud.prefdtmf = dtmfmode;
				}
			}
		} else
			ast_log(LOG_NOTICE, "Ignoring unknown H.323 [endpoints] keyword '%s'.\n", 
							v->name);
		v = v->next;
	}
	if (option_debug) {
		if (id_count)
			ast_log(LOG_DEBUG, "H.323 configuration contains %d endpoint(s).\n",
							id_count);
		else
			ast_log(LOG_DEBUG, "H.323 [endpoints] category contains no entries.\n");
	}

	/* There must be a [codecs] category, otherwise stop */
	codec_found = 0;
	frames_found = 0;
	codectmp = NULL;
	config_options.codecInfo = NULL;
	v = ast_variable_browse(cfg, "codecs");
	if (v == NULL) {
		ast_log(LOG_WARNING, "Category [codecs] not present in configuration file (%s).\n", config);
		ast_config_destroy(cfg);
		return(0);
	}
	while (v) {
		if (!strcasecmp(v->name, "codec")) {
			vcodec = get_num(codec_str, v->value);
			if (vcodec == 0) {
				ast_log(LOG_WARNING, "Unknown codec '%s'.\n", v->value);
				codec_found = 0;
			} else {
				format = oh323_codec2format(vcodec);
				if (format == 0) {
					ast_log(LOG_WARNING, "Unsupported codec '%s'.\n", v->value);
					codec_found = 0;
				} else {
					oh323_capability |= format;

					/* Allocate a new struct */
					if (codectmp != NULL)
						codecprev = codectmp;
					codectmp = (struct oh323_codecinfo*)malloc(sizeof(struct oh323_codecinfo));
					if (codectmp == NULL) {
						ast_log(LOG_ERROR, "Memory allocation failed.\n");
						return(-1);
					}
					memset(codectmp, 0, sizeof(struct oh323_codecinfo));
					codectmp->codec = vcodec;
					codectmp->format = format;
					codectmp->frames = 1;
					if (config_options.codecInfo == NULL)	/* Save the head of the list */
						config_options.codecInfo = codectmp;
					else	 /* Connect the new struct to the end of the list */
						codecprev->next = codectmp;
					codec_found = 1;
					frames_found = 0;
				}
			}
		} else if (!strcasecmp(v->name, "frames")) {
			if ((!codec_found)||(frames_found))
				ast_log(LOG_NOTICE, "Ignoring unexpected 'frames' options at line %d.\n", v->lineno);
			else {
				codectmp->frames = (int) strtol(v->value, NULL, 10);
				frames_found = 1;
				codec_found = 0;
			}
		} else
			ast_log(LOG_NOTICE, "Ignoring unknown H.323 [codecs] keyword '%s'.\n", v->name);
		v = v->next;
	}
	oh323_full_capability = oh323_capability;

	ast_config_destroy(cfg);

	return(0);
}

/**
 * Deallocate the dynamically allocated parts of the configuration
 * structure.
 */
static void destroy_config(void)
{
	struct oh323_reginfo *regtmp, *regtmp1;
	struct oh323_codecinfo *codectmp, *codectmp1;
	struct oh323_ep *exttmp, *exttmp1;
	int i;

	/* [register] section */
	regtmp = config_options.regInfo;
	while (regtmp) {
		for (i=0; i<regtmp->alias_num; i++)
			if (regtmp->alias[i])
				free(regtmp->alias[i]);
		for (i=0; i<regtmp->prefix_num; i++)
			if (regtmp->prefix[i])
				free(regtmp->prefix[i]);
		regtmp1 = regtmp->next;
		free(regtmp);
		regtmp = regtmp1;
	}

	/* [endpoints] section */
	exttmp = oh323_eplist;
	while (exttmp) {
		exttmp1 = exttmp->next;
		free(exttmp);
		exttmp = exttmp1;
	}
	oh323_eplist = NULL;

	/* [codecs] section */
	codectmp = config_options.codecInfo;
	while (codectmp) {
		codectmp1 = codectmp->next;
		free(codectmp);
		codectmp = codectmp1;
	}
	memset((char *)&config_options, 0, sizeof(config_options));
}

/**
 * Perform cleanup of the OpenH323 channel driver.
 * Return nothing.
 */
void oh323_atexit(void)
{
	int i, res;

	ast_mutex_lock(&usecnt_lock);
	res = usecnt;
	ast_mutex_unlock(&usecnt_lock);
	if (res > 0) {
		ast_log(LOG_WARNING, "OpenH323 channel driver is busy!\n");
		return;
	}

	if (option_verbose > 1)
		ast_verbose(VERBOSE_PREFIX_2 "Cleaning up OpenH323 channel driver.\n");

	ast_cli_unregister(&cli_show_conf);
	ast_cli_unregister(&cli_show_stats);
	ast_cli_unregister(&cli_show_info);
	ast_cli_unregister(&cli_show_established);
	ast_cli_unregister(&cli_show_ep);
	ast_cli_unregister(&cli_debug_toggle);
	//ast_cli_unregister(&cli_mode);
	ast_cli_unregister(&cli_vars);

	/* Unregister channel type */
	ast_channel_unregister(&oh323_tech);

	/* Kill the monitor thread */
	kill_monitor();

	/* Cleanup stuff */
	ast_mutex_lock(&oh323_tab_lock);
	for (i=0; i<config_options.totalNum; i++) {
		if (oh323_tab[i] != NULL) {
			oh323_close_call_fds(i);
			oh323_destroy(i);
			free(oh323_tab[i]);
			oh323_tab[i] = NULL;
		}
	}
	ast_mutex_unlock(&oh323_tab_lock);

	if (h323_removeall_capabilities() != CAP_REMOVEALL_OK)
		ast_log(LOG_ERROR, "Unable to remove H323 capabilities.\n");
	/*****************
	if (option_debug)
		ast_log(LOG_DEBUG, "Removing listener.\n");
	if (h323_removeall_listeners() != LIS_REMOVEALL_OK)
		ast_log(LOG_ERROR, "Unable to remove H323 listeners.\n");
	*****************/

	h323_end_point_destroy();
	destroy_config();

	if (option_debug)
		ast_log(LOG_DEBUG, "Done...\n");

	return;
}

/******************************************************************************/
/* Required ASTERISK's stuff **************************************************/

int load_module(void);
int unload_module(void);
int usecount(void);
char *description(void);
char *key(void);

int load_module(void)
{
	struct oh323_reginfo *regtmp;
	struct oh323_codecinfo *codectmp;
	int i, j, k;
	int res;

	/* Init our locks */
	ast_mutex_init(&usecnt_lock);
	ast_mutex_init(&mon_lock);
	ast_mutex_init(&oh323_uid_lock);
	ast_mutex_init(&oh323_tab_lock);
	ast_mutex_init(&oh323_stats_lock);
	ast_mutex_init(&oh323_reload_lock);
	oh323_reloading = 0;
	monitor_running = 0;
	monitor_shutdown = 0;

	oh323_sched = sched_context_create();
	if (!oh323_sched) {
		ast_log(LOG_WARNING, "Unable to create schedule context.\n");
		return(AST_MODULE_LOAD_FAILURE);
	}

	/* Register our CLI extensions */
	ast_cli_register(&cli_show_conf);
	ast_cli_register(&cli_show_stats);
	ast_cli_register(&cli_show_info);
	ast_cli_register(&cli_show_established);
	ast_cli_register(&cli_show_ep);
	ast_cli_register(&cli_debug_toggle);
	//ast_cli_register(&cli_mode);
	ast_cli_register(&cli_vars);

	/* Init the endpoints list */
	oh323_eplist = NULL;
	ast_mutex_init(&oh323_eplock);

	OBJLIST_INIT(&requestl);

	/* Load our config file */
	if (option_debug)
		ast_log(LOG_DEBUG, "Loading config file.\n");
	if (reload_config() < 0)
		return(AST_MODULE_LOAD_FAILURE);

	if (config_options.totalNum == 0) {
		ast_log(LOG_NOTICE, "Total number of allowed H.323 calls is 0! Disabling H.323 channel driver.\n");
		return(AST_MODULE_LOAD_SUCCESS);
	}
	if (config_options.codecInfo == NULL) {
		ast_log(LOG_NOTICE, "No codecs configured! Disabling H.323 channel driver.\n");
		return(AST_MODULE_LOAD_SUCCESS);
	}

	/* Init table with private structures of H.323 calls. */
	if (option_debug)
		ast_log(LOG_DEBUG, "Allocating H.323 channel space.\n");
	oh323_tab = (struct chan_oh323_pvt **)malloc(config_options.totalNum * 
												sizeof(struct chan_oh323_pvt *));
	if (oh323_tab == NULL) {
		ast_log(LOG_ERROR, "Memory allocation failed.\n");
		return(AST_MODULE_LOAD_FAILURE);
	}
	memset(oh323_tab, 0, config_options.totalNum * sizeof(struct chan_oh323_pvt *));

	/* Init the ingress call rate limiter */
	if (in_call_rate_limiter_init(config_options.crlCallNumber,
							config_options.crlCallTime) < 0) {
		return(AST_MODULE_LOAD_FAILURE);
	}
	if (option_debug)
		ast_log(LOG_DEBUG, "Ingress call rate limit set at %.2f CPS.\n", 
						in_call_max_rate);

	/* Init statistics */
	ast_mutex_lock(&oh323_stats_lock);
	oh323_stats.incall = 0;
	oh323_stats.outcall = 0;
	oh323_stats.setup_sent = 0;
	oh323_stats.setup_recv = 0;
	oh323_stats.block_incall = 0;
	oh323_stats.block_outcall = 0;
	oh323_stats.proto_err = 0;
	oh323_stats.call_err = 0;
	oh323_stats.answer_err = 0;
	if (gettimeofday(&(oh323_stats.boot_time), NULL)) {
		ast_log(LOG_ERROR, "Failed to get current system time!\n");
		ast_mutex_unlock(&oh323_stats_lock);
		return(AST_MODULE_LOAD_FAILURE);
	}
	ast_mutex_unlock(&oh323_stats_lock);

	/* Get aliases and prefixes */
	config_options.gateway_prefix_tab = NULL;
	config_options.alias_tab = NULL;
	regtmp = config_options.regInfo;
	config_options.prefix_num = 0;
	config_options.alias_num = 0;
	while (regtmp) {
		config_options.prefix_num += regtmp->prefix_num;
		config_options.alias_num += regtmp->alias_num;
		regtmp = regtmp->next;
	}
	if (config_options.prefix_num > 0) {
		config_options.gateway_prefix_tab = (char **)malloc(config_options.prefix_num * sizeof(char *));
		if (config_options.gateway_prefix_tab == NULL) {
			ast_log(LOG_ERROR, "Memory allocation failed.\n");
			return(AST_MODULE_LOAD_FAILURE);
		}
		memset(config_options.gateway_prefix_tab, 0, 
						config_options.prefix_num * sizeof(char *));
	} else {
		ast_log(LOG_DEBUG, "Zero prefix(es) for ASTERISK.\n");
	}
	if (config_options.alias_num > 0) {
		config_options.alias_tab = (char **)malloc(config_options.alias_num * sizeof(char *));
		if (config_options.alias_tab == NULL) {
			ast_log(LOG_ERROR, "Memory allocation failed.\n");
			return(AST_MODULE_LOAD_FAILURE);
		}
		memset(config_options.alias_tab, 0, config_options.alias_num * sizeof(char *));
	} else {
		ast_log(LOG_DEBUG, "Zero alias(es) for ASTERISK.\n");
	}

	regtmp = config_options.regInfo;
	j = 0;
	k = 0;
	while (regtmp) {
		for (i=0; i<regtmp->prefix_num; i++) {
			(config_options.gateway_prefix_tab)[j] = regtmp->prefix[i];
			++j;
		}
		for (i=0; i<regtmp->alias_num; i++) {
			(config_options.alias_tab)[k] = regtmp->alias[i];
			++k;
		}
		regtmp = regtmp->next;
	}

	h323_callback_register(NULL, NULL, NULL, NULL, NULL, NULL, NULL); 
	h323_appinfo_set("asterisk-oh323", OH323_VERSION_MAJOR, 
					OH323_VERSION_MINOR, OH323_VERSION_BUILD);

	/* Create our H.323 endpoint */
	h323_end_point_create(config_options.gateway_prefix_tab, 
					config_options.prefix_num, 
					config_options.wrapLibTraceLevel, 
					config_options.libTraceLevel, config_options.libTraceFile);

	/* Configure the endpoint */
	if ((h323_set_options(!config_options.fastStart,
						!config_options.h245Tunnelling,
						!config_options.h245inSetup,
						config_options.bwlimit,
						config_options.jitterMin,
						config_options.jitterMax,
#ifdef HAS_OH323MODS
						config_options.jitterDec,
						config_options.jitterInc,
#endif
						config_options.ipTos) < 0) || 
		(h323_set_ports(config_options.tcpStart, 
						config_options.tcpEnd, 
						config_options.udpStart, 
						config_options.udpEnd,
						config_options.rtpStart, 
						config_options.rtpEnd) < 0)) {
		ast_log(LOG_ERROR, "H.323 endpoint setup failed.\n");
		unload_module();
		return(AST_MODULE_LOAD_FAILURE);
	}

	/* Create the H.323 listener */
	if (h323_start_listener(TCP, config_options.listenAddress,
							config_options.listenPort) != LIS_START_OK) {
		ast_log(LOG_ERROR, "H.323 listener creation failed.\n");
		unload_module();
		return(AST_MODULE_LOAD_FAILURE);
	}

	/* Add the codecs */
	/* Note: 
	 * 		AddCapability puts the codec into the list of codecs we can send
	 * 		SetCapability puts the codec into the list of codecs we can send and receive
	 */
	h323_removeall_capabilities();
	codectmp = config_options.codecInfo;
	while (codectmp) {
		res = h323_set_capability(codectmp->codec, codectmp->frames);
		if (res != CAP_INSERT_OK) {
			ast_log(LOG_ERROR, "Failed to insert capability %d.\n", codectmp->codec);
			unload_module();
			return(AST_MODULE_LOAD_FAILURE);
		}
		codectmp = codectmp->next;
	}

	/* Set the user-input mode */
	if (h323_set_senduimode(config_options.userInputMode) != CAP_INSERT_OK) {
		ast_log(LOG_ERROR, "Failed to set user-input mode to %d.\n", config_options.userInputMode);
		unload_module();
		return(AST_MODULE_LOAD_FAILURE);
	}

	/* Gatekeeper setup */
	if (h323_set_gk(config_options.gatekeeperName,
					config_options.gatekeeperZone,
					config_options.gatekeeperPass, 
					config_options.gatekeeperTTL, 
					config_options.alias_tab, 
					config_options.alias_num)) {
		ast_log(LOG_ERROR, "H.323 gatekeeper setup failed.\n");
		unload_module();
		return(AST_MODULE_LOAD_FAILURE);
	}
	if (config_options.gatekeeperMode != GKMODE_DISABLE) {
		if (h323_reset_gk(config_options.gatekeeperName, config_options.gatekeeperZone)) {
			ast_log(LOG_ERROR, "H.323 gatekeeper setup failed.\n");
			unload_module();
			return(AST_MODULE_LOAD_FAILURE);
		}
		oh323_sched_gkid = ast_sched_add(oh323_sched, 30000, 
									oh323_gk_check, NULL);
	}

	/* Register our callback functions */
	if (h323_callback_register(setup_h323_connection, cleanup_h323_connection, 
					NULL, exception_h323_connection, 
					init_h323_connection, get_h323_userdata, NULL) < 0) {
/*					init_h323_connection, stats_h323_connection) < 0) {*/
		ast_log(LOG_ERROR, "Failed to register callback function(s).\n");
		unload_module();
		return(AST_MODULE_LOAD_FAILURE);
	}

	/* Register this channel type with Asterisk PBX. */
	oh323_tech.capabilities = oh323_full_capability;
	if (ast_channel_register(&oh323_tech)) {
		ast_log(LOG_ERROR, "Unable to register channel type %s\n", type);
		unload_module();
		return(AST_MODULE_LOAD_FAILURE);
	}

	/* Start the monitor for the first time */
	restart_monitor();

	/* Register our "atexit" function */
	ast_register_atexit(oh323_atexit);

	if (option_verbose > 1)
		ast_verbose(VERBOSE_PREFIX_2 "OpenH323 Channel Ready (v%d.%d.%d)\n", 
						OH323_VERSION_MAJOR, OH323_VERSION_MINOR, OH323_VERSION_BUILD);

	return(AST_MODULE_LOAD_SUCCESS);
}

int unload_module(void)
{
	oh323_atexit();

	/* Unregister our "atexit" function */
	ast_unregister_atexit(oh323_atexit);

	return(0);
}

int usecount(void)
{
	int res;
	ast_mutex_lock(&usecnt_lock);
	res = usecnt;
	ast_mutex_unlock(&usecnt_lock);
	return res;
}

char *description(void)
{
	return (char *)desc;
}

char *key(void)
{
	return ASTERISK_GPL_KEY;
}

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "H.323 Protocol (OH323)",
		.load = load_module,
		.unload = unload_module,
	);
/* End of file ****************************************************************/
